win32 线程知识点梳理三

本节整理的内容为:如何用一个线程控制另一个正在运行的线程?

方案一:终止一个线程,使用TerminateThread

BOOL WINAPI TerminateThread(
  _Inout_ HANDLE hThread,
  _In_    DWORD  dwExitCode
);

参数1:想要结束的线程的handle
参数2:该线程的结束代码
返回值:
如果函数成功,那么传回True,如果失败,那么返回false

看起来TerminateThread()是不错的,它以强硬的作风迫使目标线程结束,甚至不允许该线程有任何挣扎的机会,这同样也带来了很多副作业,线程没有机会在结束前清理自己。

  • 目标线程的堆栈可能没有被释放掉,这就引起了内存泄漏的问题。
  • 而且,任何一个和这个线程有关系的DLL都没有机会获得“线程解除附着”的通知。
  • 这个函数,同时还带来了另一个隐忧,如果线程进入critical section之中,那么该critical section将因此永远处于锁定状态,因为critical section不像mutex那样,有abandoned的状态。
  • 如果目标线程正在更新一份数据结构,这份数据结构也将永远处于不稳定的状态。

得出的结论是:离TerminateThread()远远的!

方案二:使用信号

这个点子似乎是不错的,因为C runtime library 支持标准的signals,如SIGABRT和SIGINT,各种signals处理函数可以利用C函数的signal() 设立之。
但是在C runtime函数中没有kill(),那是Unix系统借以送出signal的操作。虽然有一个raise(),但是只能够传送signal给目前的线程。
本质上,signals就是利用Win32的异常exceptions模拟的,Win32中没有真正的signals
所以这个方案是行不通的。

方案三:Exceptions

在目标线程中引发一个异常情况,如果有必要在结束前清理某些东西,目标线程可以设法捕捉这一异常情况,否则它可以什么都不管的终结自己的生命。

win32 API中没有标准的方法。
可以采用的技术是:利用debugging API写一些不合法指令到目标线程的目前地址上
另一种做法是改变一个常用的指针,使它指向一个不合法地址,因而强迫程序代码产生一个异常情况。

方案四:设立一个标记

win32核准的标准做法是,设立一个标记,利用其值来要求线程结束自己。
这种做法有一个明确的缺点,线程需要一个polling机制,时时检查标记值,以决定该不该结束自己。
具体做要的事情就是使用一个manual-reset的event对象,worker线程可以检查该event对象的状态或者等待它。
原书这个部分有一个测试代码。我在win32平台上测试了一下。

#define WIN32_LEAN_AND_MEAN // reduce the size of the Win32 header files
#include 
#include 
#include 
#include 
HANDLE hRequestExitEvent = false;
DWORD WINAPI ThreadFunc(LPVOID p){
    int i ;
    int inside = 0;
    UNREFERENCED_PARAMETER(p);
    srand((unsigned)time(NULL));
    for(i=0;i<10000000;i++){ //这里修改为原来的10倍,视机器而定,循环有可能过快执行完
        double x = (double)(rand())/RAND_MAX;
        double y = (double)(rand())/RAND_MAX;
        if((x*x+y*y)<=1.0)
            inside++;
        if(WaitForSingleObject(hRequestExitEvent,0)!=WAIT_TIMEOUT){
            printf("received request to terminate\n");
            return (DWORD)-1;
        }

    }
    printf("PI = %.4g\n",(double)inside/i*4);
    return 0;
}

int main(){

    HANDLE hThreads[2];
    DWORD dwThreadId;
    DWORD dwExitCode = 0;
    int i ; 
    hRequestExitEvent = CreateEvent(NULL,true,false,NULL);//manual reset ,非激发状态

    for(i=0;i<2;i++){
        hThreads[i] =  CreateThread(NULL,0,ThreadFunc,(LPVOID)i,0,&dwThreadId);
    }

    Sleep(1000);
    SetEvent(hRequestExitEvent);//设为激发状态
    WaitForMultipleObjects(2,hThreads,true,INFINITE);//所有handle都激发才能返回

    for(i=0;i<2;i++)
        CloseHandle(hThreads[i]);
    return EXIT_SUCCESS;

}

运行可以看到提示。
运行结果

这里,用event对象来取代一个简单的全局变量,这个例子中并不一定需要event对象,但如果我们使用它,worker线程可以在必要的时候等待之。
例如worker线程可以利用event对象来等待一个Internet socket连接成功,或者等待用户发出离开的请求。

另外,在main中等待所有线程的handles变为激发状态,可以保证线程都已安全地离开了。
商业软件中,可能不会用到无穷等待,而会设定一个上限值,例如15秒或30秒,通常那足以表明线程已经失去了反应。


资料来源:
win32多线程程序设计

你可能感兴趣的:(多线程)