windows核心编程之使用线程APC回调安全退出多个等待线程

前言

程序开发中经常遇到需要这些情况:辅助线程正在等待内核对象的触发,主线程需要强制终止辅助线程。我们常常做的就是使用:TerminateThread来强制终止线程。这样做当然是不太好的,强制终止线程后系统不会销毁此线程的堆栈,长久下去内存泄露问题就会很严重了。线程最安全的退出方式当然还是让它自己返回了。本文主要介绍windows核心编程中介绍的一种安全退出线程方式:使用可等待API等待内核对象触发,添加线程APC回调。

API介绍

首先得简单介绍下一个重要的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;
}

程序运行

windows核心编程之使用线程APC回调安全退出多个等待线程_第1张图片

总结

主要就是在线程等待时,使用那些扩展的API(带Ex),使线程进入可等待状态。这时,系统会先检查线程的APC队列。由于我们向线程的APC队列中人为添加了一个回调函数,队列不为空则调用APC函数,清空队列。然后函数执行完毕返回WAIT_IO_COMPLETION,这时我们就知道是自己添加了一个APC回调,引导线程自己退出。从而实现多个等待线程安全地退出等待状态。

你可能感兴趣的:(windows核心编程之使用线程APC回调安全退出多个等待线程)