干净的终止一个线程 

   我曾经在第2章产生一个后台线程,用以输出一张屏幕外的 bitmap 图。我们必须解决的一个最复杂的问题就是,如果用户企图结束程序,而这张bitmap 图尚未完成,怎么办?第2章的一个鸵鸟做法就是在任何 worker 线程还没完成其工作之前,不准用户结束程序。只要修改主消息循环,使消息循环不得在任何一个 worker 线程尚未结束之前结束,即可办到。这种做法的最大优点就是“简单”,但万一 bitmap 图十分复杂,需要很长的工作时间,那么程序有可能看起来像是“挂”了一样。

利用TerminateThread() 放弃一个线程

这正是 Win32 程序设计的一般性问题。我如何能够安全地关闭任何执行中的线程呢?最明显的答案就是利用 TerminateThread():
    BOOL TerminateThread(
        HANDLE hThread,
        DWORD dwExitCode
    );
    参数
    hThread     欲令其结束之线程的 handle。该线程就是我们的行动目标。
    dwExitCode     该线程的结束代码。
    返回值
        如果函数成功,则传回 TRUE。如果失败,则传回 FALSE。GetLastError()可以获知更多细节。
    TerminateThread() 看起来不错, 直到我读了一份文件, 上面说:“TerminateThread() 是一个危险的函数,应该在最不得已的情况下才使用”。这是一个非常明白的警告。
    TerminateThread() 强迫其行动目标(一个线程)结束,手段激烈而有力,甚至不允许该线程有任何“挣扎”的机会。这带来的副作用便是,线程没有机会在结束前清理自己。对线程而言,这可能导致前功尽弃。这个函数不会在目标线程中丢出一个异常情况(exception),目标线程在核心层面就被根本抹杀了。目标线程没有机会捕捉所谓的“结束请求”,并从而获得清理自己的机会。
    还有另一个令人不愉快的情况。目标线程的堆栈没有被释放掉,于是可能会引起一大块内存泄漏(memory leak)。而且,任何一个与此线程有附着关系的 DLLs 也都没有机会获得“线程解除附着”的通知。
    此函数唯一可以预期并依恃的是,线程 handle 将变成激发状态(译注:因为线程结束了),并且传回 dwExitCode 所指定的结束代码。
    这个函数所带来的隐伏危机还包括:如果线程正进入一个 critical section 之中,该 critical section 将因此永远处于锁定状态,因为 critical section 不像mutex 那样有所谓的 "abandoned" 状态。如果目标线程正在更新一份数据结构,这份数据结构也将永远处于不稳定状态。没有任何方法可以阻止这些问题的发生。
        我的结论是:离 TerminateThread() 远远地!

使用信号(Signals)
    下一个似乎可行的想法是使用 signals。在 Unix 系统中,signals 是跨进程传送通告(notifications)的标准方法。在 Unix 系统中 SIGTERM 相当于“请你离开”的意思,SIGKILL 则是粗略相当于 TerminateThread()。
    这个点子似乎不错,因为 C runtime library 支持标准的 signals,如SIGABRT 和 SIGINT。各种 signals 的处理函数可以利用 C 函数 signal() 设立之。
    但是我很快就进入了一个死胡同。C runtime 函数中没有一个名为 kill(),而那是 Unix 系统藉以送出 signal 的操作。是有一个 raise() 啦,但只能够传送 signal 给目前的线程。
    观察过 C runtime library 的源代码之后,我发现 signals 其实是利用Win32 的异常情况(exceptions)模拟的,Win32 之中并没有真正的 signals,所以这个想法也行不通。

跨越线程,丢出异常情况(Exceptions)
    我真正要做的就是在目标线程中引发一个异常情况(exception)。如果有必要在结束前清理某些东西,目标线程可以设法捕捉此一异常情况,否则它可以什么都不管地直接结束自己的生命。
    经过数个小时的努力之后,我可以很确定地告诉各位,Win32 API 中没有什么标准方法可以把一个异常情况丢到另一个线程中。真是不幸,因为这样的行为正是我们所需要的。
对专家而言… 关于“模拟丢出一个异常情况到另一个线程”的做法,在 Usenet 上有广泛的讨论。技术之一是利用 debugging API 写一个不合法的指令到目标线程的目前地址上。另一种做法是改变一个常用的指针,使它指向一个不合法地址,因而强迫程序代码产生一个异常情况。这两种做法都有缺点,但当你别无它途时,不妨一试。

设立一个标记
    当所有方法都失败时,不妨返朴归真,回到最简单最明白的路上。Win32 核准的做法是在你的程序代码中设立一个标记,利用其值来要求线程结束自己。
    这个技术有十分明显的优点,可以保证目标线程在结束之前有安全而一致的状态。其缺点也十分明显:线程需要一个 polling 机制,时时检查标记值,以决定该不该结束自己。此刻的你听到 “polling” 会不会有毛骨悚然的感觉?不不,我们并不是要写一个 busy loop 来检验标记值,我们的做法是使用一个手动重置(manual-reset)的 event 对象。Worker 线程可以检查该 event 对象的状态或是等待它,视情况而定。
    结束一个线程,听起来好容易,但是结束程序必须按次序进行,以避免发生 race conditions。让程序依次序进行是非常重要的,特别是在程序要结束之前。结束一个程序就好像拆除一栋建筑物一样,在你以推土机轧平它之前,你必须确定每一个人都安全离开了屋子。结束一个程序也是这样,每一个线程都被迫结束,不管它进行到哪里。
    让我们看一个简单的例子。列表5-1 的范例程序中,THRDTERM 产生两个线程,周期性地检查一个 event 对象,以决定要不要结束自己。程序代码非常类似第3章的 BUSY2。
列表5-1 THRDTERM—干净地结束一个线程
#0001 /*
#0002 * ThrdTerm.c
#0003 *
#0004 * Sample code for "Multithreading Applications in Win32"
#0005 * This is from Chapter 5, Listing 5-1
#0006 *
#0007 * Demonstrates how to request threads to exit.
#0008 *
#0009 * Build command: cl /MD ThrdTerm.c
#0010 */
#0011
#0012 #define WIN32_LEAN_AND_MEAN
#0013 #include <stdio.h>
#0014 #include <stdlib.h>
#0015 #include <windows.h>
#0016 #include <time.h>
#0017 #include "MtVerify.h"
#0018
#0019 DWORD WINAPI ThreadFunc(LPVOID);
#0020
#0021 HANDLE hRequestExitEvent = FALSE;
#0022
#0023 int main()
#0024 {
#0025         HANDLE hThreads[2];
#0026         DWORD dwThreadId;
#0027         DWORD dwExitCode = 0;
#0028         int i;
#0029
#0030         hRequestExitEvent = CreateEvent(
#0031         NULL, TRUE, FALSE, NULL);
#0032
#0033         for (i=0; i<2; i++)
#0034             MTVERIFY( hThreads[i] = CreateThread(NULL,
#0035                     0,
#0036                     ThreadFunc,
#0037                     (LPVOID)i,
#0038                     0,
#0039                     &dwThreadId )
#0040         );
#0041
#0042 // Wait around for awhile, make
#0043 // sure the thread is running.
#0044         Sleep(1000);
#0045
#0046         SetEvent(hRequestExitEvent);
#0047         WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);
#0048
#0049         for (i=0; i<2; i++)
#0050             MTVERIFY( CloseHandle(hThreads[i]) );
#0051
#0052         return EXIT_SUCCESS;
#0053 }
#0054
#0055
#0056 DWORD WINAPI ThreadFunc(LPVOID p)
#0057 {
#0058         int i;
#0059         int inside = 0;
#0060
#0061         UNREFERENCED_PARAMETER(p);
#0062
#0063 /* Seed the random-number generator */
#0064         srand( (unsigned)time( NULL ) );
#0065
#0066         for (i=0; i<1000000; i++)
#0067         {
#0068             double x = (double)(rand())/RAND_MAX;
#0069             double y = (double)(rand())/RAND_MAX;
#0070             if ( (x*x + y*y) <= 1.0 )
#0071                 inside++;
#0072             if(WaitForSingleObject(hRequestExitEvent, 0) != WAIT_TIMEOUT)
#0073             {
#0074                 printf("Received request to terminate\n");
#0075                  return (DWORD)-1;
#0076             }
#0077         }
#0078         printf("PI = %.4g\n", (double)inside / i * 4);
#0079        return 0;
#0080 }
    首先请注意,event 对象已经被用来取代一个简单的全局变量了。这个例子并不一定需要 event 对象,但如果我们使用它,worker 线程就能够在必要时候等待之。例如 worker 线程可以利用 event 对象来等待一个 Internetsocket 的连接成功,或是等待用户发出离开的请求。我们只要把上述 worker线程中的 WaitForSingleObject() 改为 WaitForMultipleObjects() 即可。
    第二个要注意的是,所有线程共用同一个 event 对象。如果程序尝试要结束,没有必要再为每一个线程产生一个各别通告机制。因为所有的线程都必须立刻被通告。其他情况下可能需要更好的控制。
    最后一点,请注意 main() 所代表的主线程,有一个 WaitForMultipleObjects()操作,等待所有的线程 handles。等待线程 handles 变成激发状态,可以保证线程都已经安全地离开了。虽然本例是无穷等待,但商业软件可能会设一个上限值,例如 15 秒或 30 秒,通常那已经足以表示线程失去了反应。

第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---干净的终止一个线程的更多相关文章

  1. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---线程优先权(Thread priority)

    有没有过这样的经验?你坐在你的车子里,目的地还在好几公里之遥,而时间已经很晚了.你拼命想告诉那些挡住你去路的人们,今天这个约会对你是多么多么重要,能不能请他们统统--呃--滚到马路外?很不幸,道路系统 ...

  2. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ----初始化一个线程

    使用线程的一个常见问题就是如何能够在一个线程开始运行之前,适当地将它初始化.初始化最常见的理由就是为了调整优先权.另一个理由是为了在SMP 系统中设定线程比较喜欢的 CPU.第10 章谈到 MFC 时 ...

  3. Android立刻终止一个线程

    /** * Created by JuTao on 2017/2/4. * 如何中止一个线程 */ public class ThreadDone { public static void main( ...

  4. Qt学习之如何启动和终止一个线程

    先来给出每个文件的相关代码然后再加以分析 //*************dialog.h**************// #ifndef DIALOG_H #define DIALOG_H #incl ...

  5. 第5章 不要让线程成为脱缰的野马(Keeping your Threads on Leash) ---简介

    这一章描述如何初始化一个新线程,如何停止一个执行中的线程,以及如何了解并调整线程优先权.    读过这一章之后,你将有能力回答一个 Win32 多线程程序设计的最基本问题.你一定曾经在 Usenet ...

  6. java中怎么终止一个线程的执行----个人学习心得

    参考了一下两个网站的介绍: ①:http://blog.csdn.net/liuhanhan512/article/details/7077601 ②:http://www.blogjava.net/ ...

  7. 在Java中如何正确地终止一个线程

    1.使用Thread.stop()? 极力不推荐此方式,此函数不安全且已废弃,具体可参考Java API文档 2.设置终止标识,例如: import static java.lang.System.o ...

  8. Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的TransactionManager)

    在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的Connection对象,我们可以通过传递Connection对象的方式达到共享的目的,但是这种做法是丑陋的.在本篇文章中,我们将引入另外 ...

  9. [转]使用VC/MFC创建一个线程池

    许多应用程序创建的线程花费了大量时间在睡眠状态来等待事件的发生.还有一些线程进入睡眠状态后定期被唤醒以轮询工作方式来改变或者更新状态信息.线程池可以让你更有效地使用线程,它为你的应用程序提供一个由系统 ...

随机推荐

  1. 为table元素添加操作日志

    1.为所有的元素添加函数onchange() <input id="status" value="${status}" onchange="ch ...

  2. Python 的经典入门书籍有哪些?

    是不是很多人跟你说,学Python开发就该老老实实地找书来看,再配合死命敲代码?电脑有了,软件也有了,心也收回来了?万事俱备,唯独只欠书籍?没找到到合适的书籍?可以看看这些. 1.Python基础教程 ...

  3. [js高手之路]深入浅出webpack教程系列4-插件使用之html-webpack-plugin配置(上)

    还记得我们上文中的index.html文件吗? 那里面的script标签还是写死的index.bundle.js文件,那么怎么把他们变成动态的index.html文件,这个动态生成的index.htm ...

  4. 【小白成长撸】--多项式求圆周率PI

    /*程序的版权和版本声明部分: *Copyright(c) 2016,电子科技大学本科生 *All rights reserved. *文件名:多项式求PI *程序作用:计算圆周率PI *作者:Amo ...

  5. ZXing生成条形码、二维码、带logo二维码

    采用的是开源的ZXing,Maven配置如下,jar包下载地址,自己选择版本下载,顺便推荐下Maven Repository <!-- https://mvnrepository.com/art ...

  6. NET Core度身定制的AOP框架

    NET Core度身定制的AOP框架 多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码 ...

  7. 团队作业8——第二次项目冲刺(Beta阶段)Day6——5.25

    1.提供当天会议照片: 2.会议的内容: (1)讨论已经完成的功能,讨论存在的问题 (2)对于界面,谈谈各自的看法 (3)讨论接下来的任务和改进的地方 3.工作安排: 队员 今日任务 明日任务 贡献比 ...

  8. Sudoku Generator

    Sudoku 算法 标签(空格分隔): 软工实践 设想:通过第一行,来生成2, 3行的排列,再通过1 - 3 生成4 - 6排列. 2 3 行的生成由上一行生成 公式为$grid[i][j] = gr ...

  9. 201521123061 《Java程序设计》第十三周学习总结

    201521123061 <Java程序设计>第十三周学习总结 1. 本周学习总结 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jm ...

  10. 201521123027 <java程序设计>第七周学习总结

    1.本周学习总结 2.书面作业 Q1.ArrayList代码分析 1.1 解释ArrayList的contains源代码 答: 源代码: //contains()方法 public boolean c ...