如何优化多线程DataProcessor类以避免锁等待阻塞问题?
我被这个问题困扰了一段时间。我有一个C++类,包含多个public函数,这些函数会被不同线程定期调用。该类的基本结构如下:
class DataProcessor { public: void process1(...); void process2(...); void process3(...); // etc... void update(); };
这是一个机器人应用,每个process函数用于处理不同传感器的数据,每当有新传感器数据传入时,就会在独立线程中调用对应的process函数。update函数运行在如下循环中:
while(true) { wait 1 second; dataProcessor.update(); }
目前我已确保各process函数可安全并发执行,但update函数不能在任何process函数执行期间调用。update函数执行速度极快,而部分process函数较为耗时,执行时间约0.1-0.2秒。
我尝试了两种安全执行策略,但都导致函数花费大量时间阻塞/等待互斥锁解锁:
第一种策略:让每个
process函数在执行前后分别锁定和解锁各自的mutex,update函数需持有所有mutex才能执行。但会出现如下问题:process1执行完成并解锁mutex 1update函数锁定mutex 1,等待mutex 2解锁process1再次被调用,需等待update函数解锁mutex 1update函数需完成执行才能解锁mutex 1,但它还在等待其他所有process函数执行完毕
这导致process1实际上需要等待所有其他process函数完成,尽管process函数本应支持并发。
第二种策略:使用计数器变量
numberOfFunctionsRunning,每个process函数在开始时递增变量,结束时递减。update函数仅在变量为0时执行。这种方式下process函数可重新并发,但update函数因变量几乎无法归零而陷入永久等待。
请问有什么线程策略能确保process函数低等待运行,同时保证update函数定期执行?
(更新:我从回答和评论中得到了一些可行思路,本周会开始尝试,有结果后再更新!)
采用准入控制+条件变量的方案,核心逻辑是:当update需要执行时,先禁止新的process启动,等待所有正在运行的process完成后执行update,执行完毕后恢复允许process正常并发。具体实现如下:
1. 类内新增控制成员
#include <mutex> #include <condition_variable> class DataProcessor { private: std::mutex mtx; std::condition_variable cv; int running_processes = 0; bool update_pending = false; // 标记是否有update等待执行 public: void process1(...); void process2(...); void process3(...); void update(); };
2. 修改process函数的执行流程
每个process启动前先检查是否有update等待,若有则暂停等待,直到update完成;若无则正常启动并更新运行计数器:
void DataProcessor::process1(...) { std::unique_lock<std::mutex> lock(mtx); // 等待update执行完成,允许新process启动 cv.wait(lock, [this](){ return !update_pending; }); running_processes++; lock.unlock(); // 释放锁,不影响其他process并发执行 // --- 原process1的业务逻辑 --- // ... // --- 业务逻辑结束 --- lock.lock(); running_processes--; // 通知update:当前process已完成 cv.notify_one(); }
所有process函数都需要按此模板修改。
3. 修改update函数的执行流程
update先标记需要执行,等待所有正在运行的process结束后执行自身逻辑,最后清除标记并恢复process的准入:
void DataProcessor::update() { std::unique_lock<std::mutex> lock(mtx); // 标记update待执行,禁止新process启动 update_pending = true; // 等待所有正在运行的process全部结束 cv.wait(lock, [this](){ return running_processes == 0; }); // --- 原update的业务逻辑 --- // ... 此处执行速度极快,不会长时间占用锁 // --- 业务逻辑结束 --- // 清除标记,允许新process启动 update_pending = false; // 通知所有等待的process可以继续执行 cv.notify_all(); }
方案优势
- process低等待:只有当
update请求存在时,新的process才会短暂等待,其余时间完全支持并发,不会出现跨process的阻塞问题。 - update定期执行:每次
update被调用时,都会快速等待现有process完成后执行,不会出现永久等待的情况。 - 性能开销低:锁仅在控制逻辑阶段持有,业务逻辑执行期间不占用锁,对process的并发效率影响极小。
补充说明
如果使用C20及以上版本,也可以用std::barrier或std::latch简化控制逻辑,但上述方案基于C11即可实现,兼容性更强。
内容的提问来源于stack exchange,提问作者Eric Foote

