日期:2014-05-16  浏览次数:21018 次

Windows下三种激活阻塞线程的方法

多线程编程经常遇到以下问题:主线程退出前,后台线程也优雅的退出。如果后台线程处于阻塞状态,则需要把阻塞线程激活。本文讨论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;
}