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

C++多线程回调注册系统异常排查:输出缺失与死锁问题求助

C++多线程回调注册系统问题排查与修复

问题现象

  1. 极少打印“Pushing callback with id”注册日志
  2. 程序偶尔挂起,推测由竞态条件导致死锁
  3. 需求目标:事件触发前允许注册回调,触发后按注册顺序执行所有回调(顺序无需严格保证)

原代码核心问题分析

1. 无保护的共享变量访问引发竞态

registerCallbackAndExecute函数中,if (!eventTriggered)的判断未加锁,而registerEvent中直接修改eventTriggered = true,两者之间存在竞态。如果主线程先执行registerEvent设置eventTriggered,注册线程会直接进入else分支执行回调,跳过注册流程,导致注册日志极少出现。

2. 条件变量与共享变量的同步错误

  • releasedCallback是跨线程共享变量,但修改和读取都未加锁,callbackCond.wait的条件判断依赖这个变量,会导致线程读到脏数据,等待逻辑失效。
  • callbackExecuted的修改和读取分别由注册线程和主线程操作,但未被同一mutex保护,eventCond.wait的唤醒条件不可靠,可能导致主线程永久等待,引发死锁。
  • 两个独立mutex(callbackMutex和eventMutex)的设计增加了同步复杂度,反而容易引发锁顺序问题。

3. 回调执行模型混乱

原设计让注册回调的线程等待被唤醒后执行回调,同时主线程等待执行完成,这种模型没必要,且放大了同步风险。

修复方案

  1. 使用单个全局mutex保护所有共享状态,避免竞态条件
  2. 调整事件触发逻辑:主线程触发事件后,直接遍历并执行所有已注册的回调
  3. 简化条件变量的使用,仅在需要等待注册完成的场景下使用(可选)
  4. 确保所有共享变量的访问都在锁的保护范围内

修复后的代码

#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>

class CallbackRegistrar{
public:
    void registerCallback(std::function<void()> callback) {
        std::unique_lock<std::mutex> lock(mtx);
        if (!eventTriggered) {
            auto saved_id = callback_id;
            std::cout << "Pushing callback with id " << saved_id << std::endl;
            registeredCallbacks.push(std::make_pair(callback_id, callback));
            ++callback_id;
        } else {
            // 事件已触发,直接执行回调
            callback();
        }
    }

    void triggerEvent() {
        std::unique_lock<std::mutex> lock(mtx);
        eventTriggered = true;
        
        // 复制回调队列到局部,避免持有锁执行回调(防止回调中再次注册引发死锁)
        std::queue<std::pair<unsigned, std::function<void()>>> callbacksCopy = registeredCallbacks;
        registeredCallbacks = {};
        
        lock.unlock(); // 提前解锁,避免回调执行时持有锁

        // 按注册顺序执行所有回调
        while (!callbacksCopy.empty()) {
            auto& cb = callbacksCopy.front().second;
            cb();
            callbacksCopy.pop();
        }
    }

private:
    std::queue<std::pair<unsigned, std::function<void()>>> registeredCallbacks;
    bool eventTriggered{false};
    std::mutex mtx; // 单个全局mutex保护所有共享状态
    unsigned callback_id{1};
};

int main()
{
    CallbackRegistrar registrar;
    std::thread t1(&CallbackRegistrar::registerCallback, std::ref(registrar), []{std::cout << "First!\n";});
    std::thread t2(&CallbackRegistrar::registerCallback, std::ref(registrar), []{std::cout << "Second!\n";});
    
    // 等待线程完成注册(如果需要确保所有注册完成再触发,可保留;否则可提前触发)
    t1.join();
    t2.join();
    
    registrar.triggerEvent();

    return 0;
}

额外说明

  • 修复后的代码将回调执行逻辑移到主线程,避免了跨线程等待的复杂同步
  • 复制回调队列到局部后解锁,防止回调函数中再次调用registerCallback时引发死锁
  • 如果需要支持事件触发后仍允许注册并立即执行,现有逻辑已覆盖该场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 15:40:38