任务完成计数模型

内容介绍

工程并发里,经常需要等待一组任务全部结束。C++ 可以用计数器、互斥锁和条件变量实现“任务完成计数”。

这个模型的性能意义是:主线程阻塞等待,不忙等;工作线程完成后只做一次轻量通知。

简化完成计数器

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
 
class WaitGroup {
public:
    void add(int n) {
        std::lock_guard<std::mutex> lock(m_);
        count_ += n;
    }
 
    void done() {
        {
            std::lock_guard<std::mutex> lock(m_);
            --count_;
        }
        cv_.notify_all();
    }
 
    void wait() {
        std::unique_lock<std::mutex> lock(m_);
        cv_.wait(lock, [&] {
            return count_ == 0;
        });
    }
 
private:
    std::mutex m_;
    std::condition_variable cv_;
    int count_ = 0;
};
 
int main() {
    WaitGroup wg;
    std::vector<std::thread> threads;
 
    for (int i = 0; i < 4; ++i) {
        wg.add(1);
        threads.emplace_back([&, i] {
            std::cout << "task " << i << '\n';
            wg.done();
        });
    }
 
    wg.wait();
 
    for (auto& t : threads) {
        t.join();
    }
}

最佳代码实践

  • add 要在线程启动前完成,避免 wait 提前返回。
  • done 最好用 RAII guard 包起来,避免异常导致计数不归零。
  • 完成计数器只负责等待,不负责传递任务和结果。
  • 需要结果时结合 cppfuture和promisecppChannel模型

常见错误用法

std::thread([&] {
    wg.add(1); // 错误倾向:在线程内部 add
    do_work();
    wg.done();
}).detach();
 
wg.wait();

问题:主线程可能先执行 wait,看到计数为 0 后直接返回。

注意事项

  • 教学版完成计数器没有防止计数变成负数。
  • 工程实现应检查非法 done、重复等待、异常路径。
  • C++20 也可以用 std::latch / std::barrier 解决部分等待场景。

学习路径