C++多线程调用spaCy-CPP解析文本时无限阻塞问题求助
核心问题1:Spacy对象生命周期错误
构造函数中创建的Spacy::Spacy spacy是局部变量,循环迭代结束后该局部对象会被销毁,释放其持有的底层Python解释器资源(spaCy-CPP基于Python封装)。你将局部对象拷贝到spacy_vector_后,拷贝对象已失去有效底层资源引用,后续在GetDoc中使用spacy_vector_[0]时,对应的底层环境已失效,直接导致nlp.parse阻塞。
另外,从局部spacy加载的nlp对象,底层依赖该局部spacy的Python环境,局部spacy销毁后,nlp对象也会变成无效状态,调用parse时必然出现异常阻塞。
核心问题2:Nlp对象的拷贝与线程安全问题
Spacy::Nlp对象并非线程安全,且不支持值拷贝(底层持有Python对象引用)。GetDoc中Spacy::Nlp nlp = nlp_queue_.front();属于值拷贝操作,会导致底层Python对象引用混乱,进一步引发阻塞或崩溃。
核心问题3:线程使用方式无效(额外问题)
RunPipeline中每次创建线程后立即调用thread.join(),这和单线程执行逻辑完全一致,根本没有实现并发效果。
修复方案
修正Spacy对象生命周期
不在构造函数的局部作用域创建Spacy对象,直接在spacy_vector_中构造,确保对象生命周期与Pipeline一致:Pipeline::Pipeline(const uint &size, std::string input_file_path, std::string output_file_name) : input_file_path_(std::move(input_file_path)), output_file_name_(std::move(output_file_name)) { spacy_vector_.reserve(size); for (auto i = 0; i < size; i++) { spacy_vector_.emplace_back(); // 直接在vector中构造Spacy对象 nlp_queue_.push(spacy_vector_.back().load("en_core_web_sm")); } }避免Nlp对象值拷贝,改用指针/引用
将nlp_queue_改为存储std::shared_ptr<Spacy::Nlp>,避免值拷贝引发的资源冲突:// 成员变量修改为: std::queue<std::shared_ptr<Spacy::Nlp>> nlp_queue_; // 构造函数中初始化: nlp_queue_.push(std::make_shared<Spacy::Nlp>(spacy_vector_.back().load("en_core_web_sm"))); // GetDoc中使用方式: auto nlp = nlp_queue_.front(); nlp_queue_.pop(); Spacy::Doc doc = nlp->parse(text); // 用指针调用parse正确实现多线程并发
先收集所有线程,最后统一等待完成,而非创建一个就阻塞等待:void Pipeline::RunPipeline() { std::ifstream jFile(input_file_path_); std::string line; std::vector<std::thread> threads; while (getline(jFile, line)) { threads.emplace_back(&Pipeline::TestGetDoc, this, line); } // 等待所有线程执行完毕 for (auto& t : threads) { if (t.joinable()) { t.join(); } } }移除冗余代码
删除GetDoc中多余的Spacy::Spacy spacy = spacy_vector_[0];,该行代码不仅无效,还会触发无效对象的拷贝操作。
内容的提问来源于stack exchange,提问作者Benjamin Dietrich

