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;
}

程序运行

总结

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

时间: 2024-10-13 12:02:12

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

Windows核心编程之创建可等待定时器及其APC回调

概述 创建可等待定时器是Windows内部线程同步的方式之一,本文简单讲述如何使用这一内核对象进行线程同步. 使用方法 创建对象: //创建事件内核对象,默认未触发状态 HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL); 设置对象属性: CreateWaitableTimer创建完成后内核对象处于未触发状态,需要使用API BOOL WINAPI SetWaitableTimer( __in HANDLE hTimer, __in con

【windows核心编程】使用远程线程注入DLL

前言 该技术是指通过在[目标进程]中创建一个[远程线程]来达到注入的目的. 创建的[远程线程]函数为LoadLibrary, 线程函数的参数为DLL名字, 想要做的工作在DLL中编写.  示意图如下:  相关API 1.创建远程线程 //该函数除了第一个参数为目标进程句柄外 //其他参数均和CreateThread一样 HANDLE hThread = CreateRemoteThread( __in HANDLE hProcess, //目标进程句柄 __in_opt LPSECURITY_A

【windows核心编程】线程局部存储TLS

线程局部存储TLS, Thread Local Storage TLS是C/C++运行库的一部分,而非操作系统的一部分. 分为动态TSL 和 静态TLS 一.动态TLS 应用程序通过调用一组4个函数来使用动态TLS, 这些函数实际上最为DLL所使用. 系统中的每个进程都有一组 正在使用标志(in-use flag), 每个标志可被设置为FREE 或者 INUSE, 表示该TLS元素是否正在使用. 微软平台保证至少有TLS_MINUMUM_AVALIABLE个标志位可供使用, TLS_MINUMU

【转】《windows核心编程》读书笔记

这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁,往往是很多单独的字句,后面的内容更为连贯. 海量细节. 第1章    错误处理 1.         GetLastError返回的是最后的错误码,即更早的错误码可能被覆盖. 2.         GetLastError可能用于描述成功的原因(CreatEvent)

《Windows核心编程》读书笔记 上

[C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁,往往是很多单独的字句,后面的内容更为连贯. 海量细节. 第1章    错误处理 1.         GetLastError返回的是最后的错误码,即更早的错误码可能被覆盖. 2.         GetLas

C++Windows核心编程读书笔记

转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔记": 关键词:c++windows 核心 编程 读书笔记 这篇笔记是我在读<windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁

《windows核心编程系列》十八谈谈windows钩子

windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功能.所谓的指定窗体并不局限于当前进程的窗体,也能够是其它进程的窗体.当监视的某一消息到达指定的窗体时,在指定的窗体处理消息之前,钩子函数将截获此消息,钩子函数既能够加工处理该消息,也能够不作不论什么处理继续传递该消息.使用钩子是实现dll注入的方法之中的一个.其它经常使用的方法有:注冊表注入,远程线

【windows核心编程】DLL相关

DLL相关的东西 1.DLL的加载方式 隐式: #pragma comment(lib, "XX.lib"); 编译器去查找名为XX.dll的DLL,除了名字相同,该DLL和该LIB的GUID也相同. 显式: HINSTANCE   hInst = LoadLibrary(TEXT("XX.dll")); if(NULL == hInst)  retrun; HINSTANCE hInst = LoadLibrary(TEXT("XX.dll")

windows核心编程 DLL技术 【转】

注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入,此种方式被称为隐式链接. 第二种方式是在程序运行时,通过调用API显式的载入所需要的DLL,并显式的链接所想要链接的符号.换句话说,程序在运行时,其中的一个线程