多线程编程经常遇到以下问题:主线程退出前,后台线程也优雅的退出。如果后台线程处于阻塞状态,则需要把阻塞线程唤醒。本文讨论Windows下面,三种唤醒阻塞线程的方法。
第一种:利用WaitForMultipleObjects函数让线程处于阻塞状态,并设置多个等待对象中的任一对象处于signal状态下都返回。此时向WaitForMultipleObjects传递的等待对象数组要包含一个额外的通知对象,主线程通过signal该通知对象,来唤醒阻塞线程。
第二种:利用MsgWaitForMultipleObjects函数让线程处于阻塞状态,并设置任何消息都可以使该函数返回。在返回后检查消息的类型,如果不是特定的通知消息,则继续调用MsgWaitForMultipleObjects;否则阻塞线程返回。此时主线程通过PostThreadMessage函数向阻塞线程发送指定的通知消息,来唤醒阻塞线程。这种方法要求阻塞线程具有消息循环,没有消息循环的线程可以通过调用PeekMessage等函数强制建立消息循环。
第三种:利用SleepEx,SignalObjectAndWait,WaitForSingleObjectEx,WaitForMultipleObjectsEx,或MsgWaitForMultipleObjectsEx函数使得线程处于alertable阻塞状态。此时主线程通过QueueUserAPC函数向阻塞线程发送一个APC对象,使得阻塞线程从阻塞中返回。
以下代码用MsgWaitForMultipleObjectsEx函数演示了这三种方法。
#include "stdafx.h"
#include <Windows.h>
#include <process.h>
#include <stdio.h>
HANDLE hStartEvent; // thread start event
HANDLE hStopEvent; // thread stop event
unsigned _stdcall ThreadProc(PVOID param)
{
// 创建消息循环,并通知主线程消息循环创建成功
MSG tmpMsg;
PeekMessage(&tmpMsg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
if (!SetEvent(hStartEvent)) //set thread start event
return 1;
HANDLE arEvents[1] = { hStopEvent } ;
while (TRUE)
{
MSG msg ;
while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
{
if (msg.message == WM_QUIT) { // quit msg
printf("quit msg received\n");
return 0 ;
}
DispatchMessage(&msg);
}
DWORD result ;
result = MsgWaitForMultipleObjectsEx( 1 , arEvents ,
INFINITE, QS_ALLINPUT, MWMO_ALERTABLE );
if (result == WAIT_OBJECT_0 + 1) // msg received
{
continue;
}
else if(result == WAIT_OBJECT_0){ // stop event
printf("stop event signaled\n");
return 0;
}
else if(result == WAIT_IO_COMPLETION ){ // apc
printf("iocp received\n");
return 0;
}
}
}
void _stdcall APCProc(ULONG_PTR dwParam)
{
// nothing to do
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hThread;
unsigned nThreadID;
hStartEvent = ::CreateEvent(0,FALSE,FALSE,0);
hStopEvent = ::CreateEvent(0, FALSE, FALSE, 0);
if(hStartEvent == 0)
{
printf("create start event failed,errno:%d\n",GetLastError());
return 1;
}
//start thread
hThread = (HANDLE)_beginthreadex( NULL, 0, &ThreadProc, NULL, 0, &nThreadID );
if(hThread == 0)
{
printf("start thread failed,errno:%d\n",GetLastError());
CloseHandle(hStartEvent);
return 1;
}
// 等待线程启动消息循环
::WaitForSingleObject(hStartEvent,INFINITE);
CloseHandle(hStartEvent);
#ifdef METHOD_EVENT
// 第一种方法,唤醒线程正在等待的事件
::SetEvent(hStopEvent);
#else
#ifdef METHOD_MSG
// 第二种方法,向线程发送消息
if( !PostThreadMessage(nThreadID, WM_QUIT, 0, 0))
{
printf("post message failed,errno:%d\n",GetLastError());
}
#else
#ifdef METHOD_APC
// 第三种方法,向线程推送一个UserAPC
if(QueueUserAPC(APCProc, hThread, 0) == 0){
printf("QueueUserAPC failed,errno:%d\n",GetLastError());
}
#else
#pragma message("Error: define METHOD_EVENT or METHOD_MSG or METHOD_APC")
#endif
#endif
#endif
// 等待线程退出
if(WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
{
printf("WaitForSingleObject failed,errno:%d\n",GetLastError());
}
CloseHandle(hThread);
return 1;
}