多线程回调函数中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
相关产品推荐
相关产品推荐

