线程间通信的方式有多种,比如函数回调、(匿名)管道、发送消息等,对于Windows多线程,Windows提供了系统API函数PostThreadMessage可以给目标线程发消息,即通过发送消息的方式实现多线程间的通信。但使用PostThreadMessage函数时有些需要注意的细节,本文就来详细地介绍一下。
在Windows系统中,我们可以调用CreateThread或__beginthreadex等函数去创建新的线程,在调用这些函数时指定线程对应的线程函数。当CreateThread或__beginthreadex返回时,只表示线程创建成功了,但线程函数不一定立即运行起来了。
有时我们需要在CreateThread或__beginthreadex返回后,等待线程函数运行起来,再进行后续操作。那我们如何知道线程函数已经运行起来呢?我们可以在调用CreateThread或__beginthreadex之前创建一个初始无信号的事件,然后把事件句柄作为参数传递给线程函数,然后在线程函数中将该事件设置为有信号。这样我们可以在CreateThread或__beginthreadex返回后,调用WaitForSIngleObject去等待这个事件。线程函数运行起来后,会将事件置为有信号,这样WaitForSIngleObject就返回了,这样主线程就知道新创建的线程的线程函数运行起来了,示例代码如下:
// 新创建的线程的线程函数
unsigned __stdcall ThreadFunc( LPVOID pParam )
{
HANDLE hWaitEvent = pParam;
// 将事件设置为有信号,告知主线程本线程函数已经运行起来
SetEvent( hWaitEvent );
// ...
return 0;
}
// 创建一个初始无信号的事件
HANDLE hWaitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
unsigned uThreadId = 0;
// 创建线程
HANDLE hThread = (HANDLE)_beginthreadex( NULL, 0, ThreadFunc, (PVOID)hWaitEvent, 0, &uThreadId );
// 设置INFINITE参数,设置无限等待,直到事件变成有信号,即线程函数已经运行起来
WaitForSingleObject( hWaitEvent, INFINITE );
CloseHandle( hWaitEvent );
此外,经常有些新人搞不清楚新创建的线程何时退出,其实很简单,无外乎有两种情况:
1)外部直接调用TerminateThread强行将线程结束了,或者时线程内部自己调用ExitThread强行退出;
2)线程函数中的代码执行完了,线程函数退出了,这样整个线程就退出了。
我们在创建新线程后,可能需要给新线程发消息(消息中携带参数),通知新线程去执行指定的业务。如果在创建线程的函数CreateThread或__beginthreadex返回时立即调用PostThreadMessage给新线程发消息,新创建的线程可能接收不到消息,可能有两个原因:
1)新线程的线程函数还没运行起来;
2)新线程的线程函数运行起来了,但新线程的消息队列还没构建起来。
对于第一个问题,上面我们已经说了解决办法。但对于第二个问题,如何知道新线程的消息队列已将建立起来了呢?我们可以调用PeekMessage强制系统立即构建新线程的消息队列,PeekMessage返回后就表示消息队列建立起来了,所以我们可以将上面例子中将事件置为有信号的代码移动到PeekMessage下面就可以,就能解决第二个问题了。相关的代码如下所示:
unsigned __stdcall ThreadFunc( LPVOID pParam )
{
MSG msg;
HANDLE hWaitEvent = pParam;
// 强制系统立即给当前线程构建消息队列
PeekMessage( &msg, NULL, WM_USER, WM_USER, PM_NOREMOVE );
// 将事件设置为有信号,通知主线程当前线程的消息队列已经构建完成,可以发消息过来了
SetEvent( hWaitEvent );
// 调用GetMessage从当前线程的消息队列中拿出消息来处理
while( ::GetMessage( &msg, NULL, 0, 0 ) )
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
switch( msg.message )
{
case MSG_XXXXXXX: // 发送过来的自定义消息
{
// do sometihing
// ....
}
break;
case WM_QUIT: // 退出
return 0;
default:
break;
}
}
return 0;
}
// 创建一个初始无信号的事件
HANDLE hWaitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
unsigned uThreadId = 0;
HANDLE hThread = (HANDLE)_beginthreadex( NULL, 0, ThreadFunc, (PVOID)hWaitEvent, 0, &uThreadId );
// 设置INFINITE参数,设置无限等待,直到事件变成有信号,即线程函数已经运行起来
WaitForSingleObject( hWaitEvent, INFINITE );
CloseHandle( hWaitEvent );
// 调用PostThreadMessage给新线程发送线程消息
PostThreadMessage( uThreadId, MSG_XXXXXXX, 0, 0 );
我们项目中也使用了PostThreadMessage,测试过程中发现有时调用PostThreadMessage给新创建的线程发送消息后,新线程并没有收到消息,导致相关业务流程出现异常。经过查看MSDN才得知我们上面分析出的原因,关于PostThreadMessage的这个问题,MSDN在该函数的说明页面中有详细的说明: