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

多线程回调函数中Mutex实现及Windows定时器并发问题咨询

问题分析与解决方案

你的核心问题是必须在CreateTimer和DeleteTimer中添加互斥锁,只在回调里加锁远远不够。下面具体说明原因和修正方案:

为什么需要在CreateTimer和DeleteTimer加锁?

你代码里的hTimerQueue、tmrHdl数组、rParam、rPipe都是全局共享资源,多线程并发访问时会出现竞态条件:

  • CreateTimer场景:如果两个线程同时调用CreateTimer创建同一个timerId的定时器,不加锁的情况下,两个线程都会判断tmrHdl[timerId]为0,进而重复调用CreateTimerQueueTimer,导致旧的定时器句柄被覆盖,后续无法正确删除,还会造成资源泄漏。另外初始化hTimerQueue时,多线程同时判断hTimerQueue == 0会重复创建定时器队列,引发错误。
  • DeleteTimer场景:DeleteTimer会修改tmrHdl数组,同时回调函数也会修改同一个数组元素。如果不加锁,可能出现DeleteTimer刚读取tmrHdl的值,回调就把它设为0,导致DeleteTimerQueueTimer操作无效句柄;或者反过来,回调刚删除定时器,DeleteTimer又重复删除,引发未定义行为。

回调函数的锁优化

你现在手动调用m.lock()和m.unlock()有风险——如果回调里的代码抛出异常(虽然当前代码没有,但后续扩展可能有),锁会无法释放,导致死锁。建议用std::lock_guard自动管理锁的生命周期,更安全。另外,DeleteTimerQueueTimer的第一个参数应该和创建时一致,用hTimerQueue而不是NULL,避免混淆默认队列和你创建的队列。

修正后的代码示例

#include <unistd.h>
#include <mutex>
#include <errno.h>
#include <windows.h>
#include <stdio.h>


typedef struct
{
    uint32_t  msgId;
    uint32_t  param1;
    uint32_t  param2;
}THRD_EVENT_T;


enum THRD_EVENTID_T
{
    THRD_MSGID_TIMER_FIRST,

    THRD_STATEA,
    THRD_STATEB,
    THRD_STATEC,
    THRD_STATED,
    THRD_STATEF,

    THRD_MSGID_TIMER_LAST
};


HANDLE   hTimerQueue = 0;
HANDLE   tmrHdl[THRD_MSGID_TIMER_LAST - THRD_MSGID_TIMER_FIRST] = { 0 };
uint32_t rParam[THRD_MSGID_TIMER_LAST - THRD_MSGID_TIMER_FIRST] = { 0 }; 
int32_t  rPipe[THRD_MSGID_TIMER_LAST - THRD_MSGID_TIMER_FIRST] = { 0 };

std::mutex m;



VOID CALLBACK TimerCallback( PVOID lpParam, bool TimerOrWaitFired )
{
    std::lock_guard<std::mutex> lock(m); // 自动加锁,作用域结束自动解锁
    if ( lpParam == NULL )
        return;

    uint32_t tmpPrm = *( uint32_t* )lpParam;
    THRD_EVENT_T tBuff;
    int txd;
    
    tBuff.msgId = tmpPrm;
    // global flags to be set go here

    // 用创建时的hTimerQueue,而非NULL
    DeleteTimerQueueTimer( hTimerQueue, tmrHdl[tmpPrm - THRD_MSGID_TIMER_FIRST], NULL );
    tmrHdl[tmpPrm - THRD_MSGID_TIMER_FIRST] = 0;
    // rPipe[tmpPrm - THRD_MSGID_TIMER_FIRST] = 0;
    // rParam[tmpPrm - THRD_MSGID_TIMER_FIRST] = 0;
}


int32_t CreateTimer( const THRD_EVENTID_T timerId, const int32_t pipeId, const uint32_t msPeriod )
{
    std::lock_guard<std::mutex> lock(m);
    int32_t ret = -1;
    if ( 0 == hTimerQueue )
    {
        hTimerQueue = CreateTimerQueue();
        if ( 0 == hTimerQueue )
        {
            DbgPrint( "FAULT - failed to create timer queue!\n" );
            return ret;
        }
    }

    size_t idx = timerId - THRD_MSGID_TIMER_FIRST;
    if ( 0 == tmrHdl[idx] )
    {
        rParam[idx] = timerId; // 确保lpParam指向的参数正确
        if ( false == CreateTimerQueueTimer( &tmrHdl[idx], hTimerQueue,
            ( WAITORTIMERCALLBACK )TimerCallback, &rParam[idx], msPeriod, 0, 0) )
        {
            printf( "FAULT (%d) - failed to create timer!\n", GetLastError() );
        }
        else
        {
            rPipe[idx] = pipeId; // 同步设置pipeId
            ret = 0;
        }
    }
    else
    {
        printf( "FAULT - timer %d already exists!\n", timerId );
        ret = -1;
    }

    return ret;
}

int32_t DeleteTimer( const THRD_EVENTID_T timerId )
{
    std::lock_guard<std::mutex> lock(m);
    int32_t ret = -1;
    size_t idx = timerId - THRD_MSGID_TIMER_FIRST;
    
    if (tmrHdl[idx] != 0) { // 先检查句柄是否有效,避免重复删除
        DeleteTimerQueueTimer( hTimerQueue, tmrHdl[idx], NULL );
        tmrHdl[idx] = 0;
        rPipe[idx] = 0;
        rParam[idx] = 0;
        ret = 0;
    }

    return ret;
}

额外注意事项

  • 所有访问全局共享资源的地方必须被同一个互斥锁保护,包括读取操作(比如判断tmrHdl[idx] == 0),因为读取和写入的并发同样会导致不一致。
  • CreateTimer里要确保rParam[idx]被正确赋值为timerId,避免回调里拿到错误的参数。
  • DeleteTimer里先检查tmrHdl[idx]是否非0,再执行删除操作,减少无效调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 03:17:10