C++主线程与子线程相互等待同步的实现方案咨询
C++主线程与子线程循环同步的解决方案
你现在要实现的是主线程和多个子线程的双向同步逻辑:
- 所有子线程必须等主线程做完
Draw(),才能启动新一轮的DoPhysics()计算 - 主线程必须等所有子线程都完成
DoPhysics(),才能开始Draw()
之前用单条件变量加互斥锁导致串行执行,问题出在你错误地让子线程在计算阶段持有锁,或者没把两个同步阶段的信号拆分开。下面给你两种可行的实现方式:
优先方案:C++20+用std::barrier
std::barrier是C++20专门新增的同步原语,天生适合这种多线程重复同步的场景。我们需要两个barrier分别管控两个同步节点:
代码实现
#include <vector> #include <thread> #include <barrier> // 全局/类成员变量,确保所有线程能访问 std::vector<Cube> cubes; const size_t thread_num = std::thread::hardware_concurrency(); // 按硬件核心数开线程 // 两个同步屏障: // 1. 子线程等Draw完成:需要thread_num个线程到达同步点 std::barrier draw_done_barrier{thread_num}; // 2. 主线程等所有子线程完成物理计算:需要thread_num+1个线程(主线程+所有子线程) std::barrier physics_done_barrier{thread_num + 1}; // 子线程执行函数 void ThreadUpdate(std::vector<Cube>::iterator start, std::vector<Cube>::iterator end) { while (true) { // 游戏主循环,实际需添加退出逻辑 // 等待主线程Draw完成 draw_done_barrier.arrive_and_wait(); // 并行处理物理计算,此处完全不需要锁,每个子线程处理独立的数组块 for (auto it = start; it != end; ++it) { DoPhysics(*it); } // 通知主线程当前子线程已完成,等待所有兄弟线程同步 physics_done_barrier.arrive_and_wait(); } } // 主线程Update函数 void MainUpdate() { // 初始化子线程,拆分数组块 std::vector<std::thread> threads; size_t chunk_size = cubes.size() / thread_num; for (size_t i = 0; i < thread_num; ++i) { auto start = cubes.begin() + i * chunk_size; auto end = (i == thread_num - 1) ? cubes.end() : start + chunk_size; threads.emplace_back(ThreadUpdate, start, end); } while (true) { // 游戏主循环 // 等待所有子线程完成物理计算 physics_done_barrier.arrive_and_wait(); // 主线程执行绘制 Draw(); // 触发所有子线程开始下一轮计算,主线程无需在此等待 draw_done_barrier.arrive_and_drop(); } // 实际项目需添加退出逻辑,此处省略线程join for (auto& t : threads) { t.join(); } }
为什么不会串行?
arrive_and_wait()只是让线程在同步点等齐,等所有线程都到达后就一起放行,子线程执行DoPhysics()时完全不持有任何锁,自然是并行运行。两个barrier把“等Draw完成”和“等物理计算完成”拆成两个独立的同步阶段,逻辑清晰且高效。
兼容C++11/17的方案:条件变量+原子变量
如果没法升级到C++20,可以用两个条件变量配合原子变量实现,核心是子线程拿到同步信号后立即解锁,让其他线程也能启动计算:
代码实现
#include <vector> #include <thread> #include <condition_variable> #include <atomic> std::vector<Cube> cubes; const size_t thread_num = std::thread::hardware_concurrency(); std::mutex mtx; std::condition_variable cv_physics_done; // 主线程等待物理计算完成的条件变量 std::condition_variable cv_draw_done; // 子线程等待Draw完成的条件变量 std::atomic<bool> draw_finished = true; // 初始状态:Draw已完成,子线程可启动第一次计算 std::atomic<size_t> done_threads = 0; // 统计完成物理计算的子线程数 void ThreadUpdate(std::vector<Cube>::iterator start, std::vector<Cube>::iterator end) { while (true) { // 等待主线程Draw完成 std::unique_lock<std::mutex> lock(mtx); cv_draw_done.wait(lock, []{ return draw_finished.load(); }); lock.unlock(); // 立即解锁,避免阻塞其他子线程启动 // 并行处理物理计算 for (auto it = start; it != end; ++it) { DoPhysics(*it); } // 通知主线程当前子线程已完成,最后一个完成的子线程唤醒主线程 if (done_threads.fetch_add(1) + 1 == thread_num) { std::lock_guard<std::mutex> lock(mtx); cv_physics_done.notify_one(); } // 等待下一轮Draw完成的信号(用!draw_finished判断避免虚假唤醒) std::unique_lock<std::mutex> lock2(mtx); cv_draw_done.wait(lock2, []{ return !draw_finished.load(); }); lock2.unlock(); } } void MainUpdate() { std::vector<std::thread> threads; size_t chunk_size = cubes.size() / thread_num; for (size_t i = 0; i < thread_num; ++i) { auto start = cubes.begin() + i * chunk_size; auto end = (i == thread_num - 1) ? cubes.end() : start + chunk_size; threads.emplace_back(ThreadUpdate, start, end); } while (true) { // 等待所有子线程完成物理计算 std::unique_lock<std::mutex> lock(mtx); cv_physics_done.wait(lock, []{ return done_threads.load() == thread_num; }); // 重置状态,准备下一轮循环 done_threads.store(0); draw_finished.store(false); lock.unlock(); // 执行绘制 Draw(); // 通知所有子线程:Draw已完成,可启动下一轮计算 std::lock_guard<std::mutex> lock2(mtx); draw_finished.store(true); cv_draw_done.notify_all(); } for (auto& t : threads) { t.join(); } }
关键注意事项
- 子线程拿到
cv_draw_done的信号后必须立刻解锁互斥锁,否则其他子线程会被阻塞,导致串行执行 - 用原子变量
done_threads统计完成数,比在锁内计数性能更优 - 两个条件变量分开管控两个等待事件,避免逻辑混乱和虚假唤醒
踩坑提醒
- 绝对不要让子线程在执行
DoPhysics()时持有互斥锁,这是导致串行的核心原因 - 别用单个条件变量处理两种不同的同步需求,很容易出现逻辑bug
- 能升级到C++20就尽量用
std::barrier,代码简洁且无需自己处理复杂的状态判断
内容的提问来源于stack exchange,提问作者Isaac Dexter
相关产品推荐
相关产品推荐

