zoukankan      html  css  js  c++  java
  • 多线程编程之线程安全退出(原创)---by cacorot

    多线程编程之线程安全退出(原创)---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得到的objectETHREAD

    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

  • 相关阅读:
    Debian 9 更换源
    MySqlDataAdapter.Fill() 报异常‘给定关键字不在字典中’的解决方案
    阿里云函数计算 .NET Core 初体验
    TimeSpan 的 Milliseconds 和 TotalMilliseconds 有啥区别?
    使用 gitee 托管你的 go 模块
    markdown的css样式(自己写的)
    markdown的流程图实现和代码语法着色
    Python元组与字典详解
    centos7的防火墙(firewalld)
    centos7 安装java和tomcat9
  • 原文地址:https://www.cnblogs.com/cheng07045406/p/3361617.html
Copyright © 2011-2022 走看看