多线程编程之线程安全退出(原创)---by cacorot
在多线程编程的时候,我们会建立线程去完成某项任务。例如杀毒软件点击开始后,就会创建一个线程开始杀毒。如果想取消杀毒,就会通过另外一个按钮来结束这个线程。但是该如何结束杀毒线程呢?
如果在windows下,该操作系统在ring3层提供了一个函数TerminateThread。原型如下
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
该函数能够“杀死”任何线程。hThread标识了要终止的那个线程的句柄。线程终止运行时,其退出代码将变成你作为dwExitCode参数传递的值。同时线程的内核对象的使用计数会递减。我在自己的一个模拟环境下试了一下,是可以停止的。我自己的环境是搜索指定目录下的文件,终止搜索的时候编辑框里的内容会停止变动。
HANDLE hcp;
void CMyDlg::OnBnClickedTest(){
hcp = CreateThread(NULL,0,CounerFunc,NULL,0,NULL);
//CounterFunc回调函数被调用,主要负责搜索目录功能。
…
}
void CMyDlg::OnBnClickedStop(){
DWORD *p = new DWORD[1];
GetExitCodeThread( hcp, p );
TerminateThread(hcp,*p);
delete []p;
} //使用该函数结束开始线程。
但是一个设计良好的应用程序决不会使用这个函数,因为被终止运行的线程收不到它被“杀死”的通知。线程无法正确清理,而且不能阻止自己被终止运行。(windows核心编程第五版)。此外,除非拥有此线程的进程终止运行,否则系统不会销毁这个线程的堆栈。
正确的方式就是让线程自己正常返回。如何正常返回呢?在结束线程里通过全局变量的形式或者一个事件的形式来告诉另外一个线程。
代码可如下表示:
HANDLE hcp;
HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);//手动触发,开始未触发状态
DWORD WINAPI CounerFunc (LPVOID lpParameter)
{
while(TRUE){
……
for(unsigned int i = 0; i < g_dlg->ivectorSize(); i++) //主要负责搜索目录
{
if(WaitForSingleObject(hEvent,0) == WAIT_OBJECT_0){
CloseHandle(hEvent);
CloseHandle(hcp);
return 0;
} //使用WaitForSingleObject等待事件内核对象被触发,一旦被触发,返回//值便是WAIT_OBJECT_0。接着线程通过return 0直接返回
……
//如果没有通知该线程技术,继续做自己的工作
CheckFile(g_dlg->ivectorItem(i)); ///检查文件
}
……
}
return 0;
}
void CMyDlg::OnBnClickedTest(){
hcp = CreateThread(NULL,0,CounerFunc,NULL,0,NULL);
//CounterFunc回调函数被调用,主要负责搜索目录功能。
…
}
void CMyDlg::OnBnClickedStop(){
SetEvent(hEvent);//触发事件内核对象!
} //结束线程
另外一种方法就是设置一个BOOL类型的全局变量。开始时该值为FALSE,在结束线程里将改变量设为TRUE。
现在说一下线程函数返回时做了哪些工作。
1. 线程函数中创建的所有C++对象都通过其析构函数被正确销毁。
2. 操作系统正确释放线程栈使用的内存。
3. 操作系统把线程的退出代码设为线程函数的返回值。
4. 系统递减线程的内核对象的使用计数。
此外,为了加深我对TerminateThread函数了理解,稍微逆向了一下。它是通过
TermiateThread->ZwTerminateThread->NtTerminateThread
如果终止当前线程,直接调用PspTerminateThreadPointer
否则使用ObReferenceObjectByHandle获取被终止线程的对象指针,再调用PspTerminateThreadPointer
使用Windbg查看了一下该函数如下
kd> u NtTerminateThread
nt!NtTerminateThread:
805c9e24 mov edi,edi
805c9e26 push ebp
805c9e27 mov ebp,esp
805c9e29 push ecx
805c9e2a push ebx
805c9e2b push esi
805c9e2c push edi
805c9e2d xor edi,edi
805c9e2f mov eax,dword ptr fs:[00000124h] //获取指向当前ETHREAD指针
805c9e35 cmp dword ptr [ebp+8],edi
805c9e38 mov esi,eax
805c9e3a jne nt!NtTerminateThread+0x2b (805c9e4f)
//跳到 805c9e4f cmp dword ptr [ebp+8],0FFFFFFFEh
805c9e3c mov eax,dword ptr [esi+44h] //获取当前EPROCESS结构
805c9e3f cmp dword ptr [eax+1A0h],1 // 活动线程数目
805c9e46 jne nt!NtTerminateThread+0x67 (805c9e8b)
805c9e48 mov eax,0C00000DBh
//如果等于1,结束退出
805c9e4d jmp nt!NtTerminateThread+0x86 (805c9eaa)
805c9e4f cmp dword ptr [ebp+8],0FFFFFFFEh
805c9e53 je nt!NtTerminateThread+0x67 (805c9e8b)
805c9e55 al,byte ptr [esi+140h]
805c9e5b push 0
805c9e5d mov byte ptr [ebp-4],al
805c9e60 lea eax,[ebp+8]
805c9e63 push eax //PVOID *Object
805c9e64 push dword ptr [ebp-4]
805c9e67 push dword ptr [nt!PsThreadType (8055b25c)]
805c9e6d push 1
805c9e6f push dword ptr [ebp+8] //Handle
//根据提供的Handle 值得到 Object!
805c9e72 call nt!ObReferenceObjectByHandle (805b1ab6)
805c9e77 mov edi,eax
805c9e79 test edi,edi
805c9e7b jl nt!NtTerminateThread+0x84 (805c9ea8)
805c9e7d mov ebx,dword ptr [ebp+8]
805c9e80 cmp ebx,esi
//如果不是当前线程,跳到805c9e96 push dword ptr [ebp+0Ch]
805c9e82 jne nt!NtTerminateThread+0x72 (805c9e96)
805c9e84 mov ecx,ebx
805c9e86 call nt!ObfDereferenceObject (80523b62)
//ExitStatus = STATUS_SUCCESS,当前线程
805c9e8b push dword ptr [ebp+0Ch]
//ETHREAD指针,WINXP下是两个参数
805c9e8e push esi
805c9e8f call nt!PspTerminateThreadByPointer (805c9b02)
805c9e94 jmp nt!NtTerminateThread+0x84 (805c9ea8)
805c9e96 push dword ptr [ebp+0Ch] 不是当前线程
805c9e99 push ebx //指向根据handle得到的object(ETHREAD)
805c9e9a call nt!PspTerminateThreadByPointer (805c9b02)
805c9e9f mov ecx,ebx
805c9ea1 mov edi,eax
805c9ea3 call nt!ObfDereferenceObject (80523b62)
805c9ea8 mov eax,edi
805c9eaa pop edi
805c9eab pop esi
805c9eac pop ebx
805c9ead leave