You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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统计完成数,比在锁内计数性能更优
  • 两个条件变量分开管控两个等待事件,避免逻辑混乱和虚假唤醒

踩坑提醒

  1. 绝对不要让子线程在执行DoPhysics()时持有互斥锁,这是导致串行的核心原因
  2. 别用单个条件变量处理两种不同的同步需求,很容易出现逻辑bug
  3. 能升级到C++20就尽量用std::barrier,代码简洁且无需自己处理复杂的状态判断

内容的提问来源于stack exchange,提问作者Isaac Dexter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 03:17:11