程序开发中经常遇到需要这些情况:辅助线程正在等待内核对象的触发,主线程需要强制终止辅助线程。我们常常做的就是使用:TerminateThread来强制终止线程。这样做当然是不太好的,强制终止线程后系统不会销毁此线程的堆栈,长久下去内存泄露问题就会很严重了。线程最安全的退出方式当然还是让它自己返回了。本文主要介绍windows核心编程中介绍的一种安全退出线程方式:使用可等待API等待内核对象触发,添加线程APC回调。
首先得简单介绍下一个重要的windows API
DWORD WINAPI QueueUserAPC( __in PAPCFUNC pfnAPC, __in HANDLE hThread, __in ULONG_PTR dwData );
函数允许我们手动将一个APC回调添加到指定线程队列中,回调函数:VOID WINAPI ApcCallback1(ULONG_PTR dwParam)
这个线程可以是系统中任何线程,如果标识的线程在另一个进程中,那么回调函数的地址也必须在另一个线程的地址空间中。
QueueUserAPC也可以用来强制让线程退出等待状态,当线程正在等待内核对象触发,可以使用它强制唤醒正在等待的线程,并让它将自己杀死。
另外就是Windows提供了6个API,可将线程的状态设置为可提醒状态。
SleepEx(
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
WaitForSingleObjectEx(
__in HANDLE hHandle,
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
WaitForMultipleObjectsEx(
__in DWORD nCount,
__in_ecount(nCount) CONST HANDLE *lpHandles,
__in BOOL bWaitAll,
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
SignalObjectAndWait(
__in HANDLE hObjectToSignal,
__in HANDLE hObjectToWaitOn,
__in DWORD dwMilliseconds,
__in BOOL bAlertable
);
GetQueuedCompletionStatusEx
MsgWaitForMultipleObjectsEx 使用MWMO_ALERTABLE标识线程进入可等待状态
这五个函数的老版本API,即不带Ex的在内部实现都是调用对应的Ex函数,传入bAlertable=FALSE。
在可提醒I/O中将对这些函数的调用以及完成回调、APC回调进行详细的介绍,这里暂时省略了。
//给每一个线程标记上一个索引值 volatile LONG g_nIndex = 0; UINT __stdcall Thread1(void* lpParam) { //使用原子锁来操作实现用户模式下的线程同步,更加快捷 LONG lPrevIndex = InterlockedExchangeAdd(&g_nIndex, 1);//我们需要记录原始值 LONG lCurIndex = lPrevIndex + 1; cout<<"线程"<<lCurIndex<<"开始执行"<<endl; while( true ) { //cout<<"Thread1:"<<nIndex<<endl; //休眠一秒钟,线程进入可提醒状态 if ( WAIT_IO_COMPLETION == SleepEx(1000, TRUE) ) {//此时,我们添加一项到APC队列中了,线程可以退出了 break; } //nIndex++; } cout<<"线程"<<lCurIndex<<"正常退出"<<endl; return 0; } //APC回调函数 VOID WINAPI ApcCallback(ULONG_PTR dwParam) { //由于只是为了让线程立即正常退出,而不必杀死线程,我们在这里可以不做任何处理 cout<<"ApcCallback:线程的APC回调函数执行"<<endl; } bool ApcTest() { const int nThreadCount = 10; HANDLE hThread[nThreadCount]; int i = 0; for ( int i=0; i<nThreadCount; ++i ) hThread[i] = (HANDLE)_beginthreadex(NULL, 0, Thread1, NULL, 0, NULL); DWORD dwError, dwRet; while( true ) { dwRet = WaitForMultipleObjects(nThreadCount, hThread, TRUE, 10*1000); if ( dwRet == WAIT_TIMEOUT ) {//等待超时,想办法强制终止线程 //由于辅助线程休眠时进入可等待状态,手动添加回调到APC队列将导致毁掉执行完后线程清空队列,线程退出可提醒状态 //此时,友好地退出线程。 for ( i=0; i<nThreadCount; ++i ) { dwRet = QueueUserAPC(ApcCallback, hThread[i], 2015); if ( dwRet == 0 ) {//添加APC回调到线程APC队列失败 dwError = GetLastError(); break; } } continue; } if ( WAIT_OBJECT_0 == dwRet ) { cout<<"辅助线程均已退出,循环退出…………"<<endl; break; } } for ( i=0; i<nThreadCount; ++i ) CloseHandle(hThread[i]); return true; }
主要就是在线程等待时,使用那些扩展的API(带Ex),使线程进入可等待状态。这时,系统会先检查线程的APC队列。由于我们向线程的APC队列中人为添加了一个回调函数,队列不为空则调用APC函数,清空队列。然后函数执行完毕返回WAIT_IO_COMPLETION,这时我们就知道是自己添加了一个APC回调,引导线程自己退出。从而实现多个等待线程安全地退出等待状态。