CreateTimerQueueTimer 回调和竞争条件
Posted
技术标签:
【中文标题】CreateTimerQueueTimer 回调和竞争条件【英文标题】:CreateTimerQueueTimer callback and race condition 【发布时间】:2009-01-24 23:18:21 【问题描述】:我在我的应用程序中使用计时器队列,并将指向我自己的 C++ Timer 对象之一的指针作为“参数”传递给回调(在 CreateTimerQueueTimer 中)。然后我在回调中调用对象的虚方法。
Timer 对象的析构函数将确保使用 DeleteTimerQueueTimer() 取消计时器。
static void callback( PVOID param, BOOLEAN timerOrWaitFired )
Timer* timer = reinterpret_cast< Timer* >( param );
timer->TimedOut();
class Timer
public:
Timer();
virtual ~Timer()
::DeleteTimerQueueTimer( handle );
void Start( double period )
::CreateTimerQueueTimer( &handle, ..., &callback, this, ... );
virtual void TimedOut() = 0;
...
;
但是,有一个微妙的竞争条件,如果回调已经被调用,但计时器对象在调用 TimedOut() 之前 被销毁,应用程序崩溃,因为回调调用虚拟不存在的对象上的方法。或者更糟糕的是,它正在被删除。
我确实有互斥锁来控制多线程调用,但我仍然遇到问题。
使用对象指针作为回调参数真的是个好主意吗?由于无法保证线程之间的同步,这对我来说只是闻起来很糟糕。
有没有更好的解决方案?其他人是做什么的?
发生的一件事是保留一组指向每个 Timer 实例的指针(在构造函数中添加,在析构函数中删除)。但我认为这行不通,因为如果 Timer 派生自,我们只会从基类析构函数的集合中删除指针;如果我们已经开始销毁派生对象,则损坏已经造成。
干杯。
【问题讨论】:
【参考方案1】:使用对象指针作为回调函数参数的概念本身还不错。但是,您显然需要在最后一个回调退出后开始销毁。
所以,我根本不会将 Timer 抽象化并从中派生。我会使用另一个抽象类TimerImpl
并使Timer
类使用TimerImpl
实例:
class Timer
TimerInstance* impl;
void TimeOut() impl->TimeOut();
public:
~Timer()
... make sure the timer has ended and wont fire again after this line...
delete impl;
struct TimerImpl
virtual void TimeOut()=0;
virtual ~TimerImpl();
这样,你可以确保直到你说完才开始破坏。
第二件事是,你必须等待最后一个计时器事件烧完。根据MSDN doc,可以调用
DeleteTimerQueueTimer(TimerQueue, Timer, INVALID_HANDLE_VALUE)
【讨论】:
【参考方案2】:当您调用 DeleteTimerQueueTimer 时,请确保您传递了 INVALID_HANDLE_VALUE 为完成事件。这将阻塞你的析构函数,直到所有挂起的回调都完成或取消。
例如
virtual ~Timer()
::DeleteTimerQueueTimer( timerQueue, handle, INVALID_HANDLE_VALUE );
这意味着您的代码将阻塞,直到所有计时器回调完成或取消。然后,您可以照常进行销毁。不过需要注意的一件事 - 你不能从同一个定时器回调中调用 deletetimerqueuetimer ,否则你会死锁。
我相信仅此一项就足以防止您遇到的竞争状况。
【讨论】:
一个潜在的问题是当调用DeleteTimerQueueTimer
函数时,任何派生类都已被销毁。当访问这些被破坏的成员时,任何引用派生类的函数成员(或者是派生类的成员)的活动回调都会导致未定义的行为。【参考方案3】:
您几乎可以肯定不能使用继承模型来做到这一点。主要问题是,在输入基类构造函数时,派生对象已经无效,但计时器可能会触发,并且没有任何东西阻止它尝试调用虚函数,这将导致未定义的行为。
我认为这样做的方法是这样的包装器。关键是要确保在尝试调度“超时”事件时不存在竞争条件。
这个实现仍然有一个缺陷。当计时器对象开始被删除时,计时器事件可能正在等待。析构函数可以释放互斥锁,然后在计时器线程等待互斥锁时销毁互斥锁。我们已经阻止了派发“超时”事件的竞争,但是等待被销毁的互斥锁的线程的行为取决于互斥锁的实现。
static void callback( PVOID param, BOOLEAN timerOrWaitFired );
class TimerWrapper
public:
/* Take reference to std::auto_ptr to ensure ownership transfer is explicit */
TimerWrapper( std::auto_ptr<Timer>& timer ) : timer_(timer)
::CreateTimerQueueTimer( &htimer_, ..., callback, this, ... );
void TimedOut()
ScopedGuard guard( mutex_ );
if( timer_.get() )
timer_->TimedOut();
~TimerWrapper()
::DeleteTimerQueueTimer( htimer_, ... );
ScopedGuard guard( mutex_ );
timer_.reset();
private:
Mutex mutex_;
std::auto_ptr<Timer> timer_;
HANDLE htimer_;
;
static void callback( PVOID param, BOOLEAN timerOrWaitFired )
TimerWrapper* timer = reinterpret_cast< TimerWrapper* >( param );
timer->TimedOut();
【讨论】:
以上是关于CreateTimerQueueTimer 回调和竞争条件的主要内容,如果未能解决你的问题,请参考以下文章