线程天敌TerminateThread与SuspendThread

作者:童磊(magictong)

目的:不是演示TerminateThread和SuspendThread的原理而是希望能在自己的程序中摒弃它们。

一、不使用TerminateThread的N条理由(先YY一下)

1、如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不会撤销该线程的执行堆栈。原因是:如果其它正在执行的线程去引用被强制撤销的线程的堆栈上的值,那么其它的线程就会出现访问违规的问题。

2、如果线程正在进行堆分配时被强行撤销,可能会破坏堆,造成堆锁没有释放,如果这时其他线程进行堆分配就会陷入死锁。

3、来之MSDN的血淋淋的警告:

a. If the target thread owns a critical section, the critical section will not be released.(如果目标线程持有着一个临界区(critical section),这临界区将不会被释放。)

b. If the target thread is allocating memory from the heap, the heap lock will not be released. (如果目标线程正在堆里分配内存,堆锁(heap lock)将不会被释放。)

c. If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent. (如果目标线程在结束时调用了某些kernel32,会造成kernel32的状态不一致。)

d. If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL. (如果目标线程正在更改一个共享DLL的全局状态,这个共享DLL的状态可能会被破坏,影响到其他的正在使用这个DLL库的线程。

4、看来,TerminateThread作为终止一个线程的最后的杀招,微软也是不建议大家使用的。但是TerminateThread完全无用么?一般说来确实如此,但是如果你非常清楚你的线程在干什么,并且能够通过代码使线程在TerminateThread的时候优雅的结束,那么你可以用,但是你真的清楚么?在一个大型的工程项目中,试想,拥有10个线程、3个临界区、3个事件触发器的程序中,开发者能清楚线程在任何时刻都在干嘛么?

5、也许结束一个线程最可靠的方法就是确定这个线程不休眠无限期的等待下去。一个支持可以被要求停止的线程,它必须定期的检查看它是否被要求停止或者如果它在休眠的话看它是否能够被唤醒。支持这两个情况最简单的的方法就是使用同步对象,这样应用程序的主线程和工作中的线程能互相沟通。当应用程序希望关闭线程时,只要设置这个同步对象,等待线程退出。之后,线程把这个同步对象作为它周期性监测的一部分,定期检查,或者如果这个同步对象的状态改变的话,就可以执行清理操作并退出了。

二、简单的演示例子

TerminateThread

void CThreadDeadlocksDlg::OnBnClickedBtnTerminatethread()

{

HANDLE hThread = ::CreateThread(NULL, 0, TerminateThreadHandle, NULL, 0, NULL);

Sleep(1000);

::TerminateThread(hThread, 0);

hThread = NULL;

char* p = new char[4096];

delete p;

return;

}

// 一个循环分配内存的线程

DWORD __stdcall CThreadDeadlocksDlg::TerminateThreadHandle(void* )

{

while(1)

{

char* p = new char[4096];

delete p;

}

}

看一下callstack和locks情况,就比较清楚了。

0:000> !locks

CritSec ntdll!LdrpLoaderLock+0 at 7c99e178

LockCount          0

RecursionCount     1

OwningThread       55c

EntryCount         1

ContentionCount    1

*** Locked

CritSec +3b0608 at 003b0608

LockCount          2

RecursionCount     0

OwningThread       0

EntryCount         2

ContentionCount    2

*** Locked

0:000> ~* kb

.  0  Id: 1540.12ac Suspend: 1 Teb: 7ffdf000 Unfrozen

ChildEBP RetAddr  Args to Child

0012f57c 7c92df5a 7c93ac7b 00000110 00000000 ntdll!KiFastSystemCallRet

0012f580 7c93ac7b 00000110 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc

0012f608 7c921046 003b0608 7c930dd0 003b0608 ntdll!RtlpWaitForCriticalSection+0x132

0012f610 7c930dd0 003b0608 00001000 00000000 ntdll!RtlEnterCriticalSection+0x46

0012f83c 78134d83 003b0000 00000000 00001000 ntdll!RtlAllocateHeap+0x2f0

0012f85c 783095c4 00001000 0012fe90 00000114 MSVCR80!malloc+0x7a [f:/dd/vctools/crt_bld/self_x86/crt/src/malloc.c @ 163]

SuspendThread也有同样的问题(SuspendThread本身也是比较暴力的):

http://blog.csdn.NET/magictong/archive/2009/05/08/4161571.aspx这里讲解了一个使用SuspendThread造成主线程LoadLibrary里面hang的例子。

void CThreadDeadlocksDlg::OnBnClickedBtnSuspendthread()

{

HANDLE hThread = ::CreateThread(NULL, 0, SuspendThreadHandle, NULL, 0, NULL);

Sleep(1000);

::SuspendThread(hThread);

hThread = NULL;

char* p = new char[4096];

delete p;

return;

}

// 一个会被挂起的线程

DWORD __stdcall CThreadDeadlocksDlg::SuspendThreadHandle(void* )

{

while(1)

{

char* p = new char[4096];

delete p;

}

}

0:000> !locks

CritSec ntdll!LdrpLoaderLock+0 at 7c99e178

LockCount          0

RecursionCount     1

OwningThread       1fdc

EntryCount         2

ContentionCount    2

*** Locked

CritSec +3b0608 at 003b0608

LockCount          2

RecursionCount     1

OwningThread       1844

EntryCount         2

ContentionCount    2

*** Locked

.  0  Id: 1a48.1900 Suspend: 1 Teb: 7ffdf000 Unfrozen

ChildEBP RetAddr  Args to Child

0012f57c 7c92df5a 7c93ac7b 00000108 00000000 ntdll!KiFastSystemCallRet

0012f580 7c93ac7b 00000108 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc

0012f608 7c921046 003b0608 7c930dd0 003b0608 ntdll!RtlpWaitForCriticalSection+0x132

0012f610 7c930dd0 003b0608 00001000 00000000 ntdll!RtlEnterCriticalSection+0x46

0012f83c 78134d83 003b0000 00000000 00001000 ntdll!RtlAllocateHeap+0x2f0

0012f85c 783095c4 00001000 0012fe90 000000f8 MSVCR80!malloc+0x7a

2  Id: 1a48.1844 Suspend: 2 Teb: 7ffdd000 Unfrozen

ChildEBP RetAddr  Args to Child

012afe88 7c93080b 003b0000 003b8638 012aff40 ntdll!RtlpCoalesceFreeBlocks+0x373

012aff5c 78134c39 003b0000 00000000 003bc750 ntdll!RtlFreeHeap+0x2e9

012affa8 004014b0 003bc750 00001000 7c80b729 MSVCR80!free+0xcd

012affb4 7c80b729 00000000 74680000 003a0043 ThreadDeadlocks!CThreadDeadlocksDlg::SuspendThreadHandle+0x10

012affec 00000000 004014a0 00000000 00000000 kernel32!BaseThreadStart+0x37

三、常驻进程里面遇到的问题

当然实际coding的过程不会像上面的代码那么暴力,但是就不会有问题了吗?当然不是的,只是碰到的概率降低了而已,问题依然存在(没有PDB了,杯具)。

第一个dmp

.  0  Id: e50.a20 Suspend: 1 Teb: 7ffdf000 Unfrozen

ChildEBP RetAddr  Args to Child

WARNING: Stack unwind information not available. Following frames may be wrong.

0012fc08 77962148 00000000 00000000 10000000 ntdll!KiFastSystemCallRet

0012fc30 7798c908 77a07340 7793cf13 0012fdb0 ntdll!EtwEventEnabled+0xd9

0012fc70 75b588bc 10000000 00000000 0012fd60 ntdll!LdrUnloadDll+0x2a

0012fc80 0041dd09 10000000 0041e0b6 00000401 KERNELBASE!FreeLibrary+0x82

0012fd60 76a5c5e7 0041d530 00370524 00000401 TSVulFWMan+0x1dd09

0012fdd8 76a54f0e 00000000 0041d530 00370524 USER32!gapfnScSendMessage+0x2cf

0012fe34 76a54f7d 006ffb98 00000401 00000000 USER32!GetScrollBarInfo+0xfd

0012fe5c 77976fee 0012fe74 00000018 0012ff78 USER32!GetScrollBarInfo+0x16c

0012fea8 0041d4eb 0012fecc 00000000 00000000 ntdll!KiUserCallbackDispatcher+0x2e

0012ff88 76233c45 7ffdd000 0012ffd4 779937f5 TSVulFWMan+0x1d4eb

0012ff94 779937f5 7ffdd000 7793ccb7 00000000 kernel32!BaseThreadInitThunk+0x12

0012ffd4 779937c8 00426dfb 7ffdd000 00000000 ntdll!RtlInitializeExceptionChain+0xef

0012ffec 00000000 00426dfb 7ffdd000 00000000 ntdll!RtlInitializeExceptionChain+0xc2

第二个dmp

0  Id: f54.3e8 Suspend: 1 Teb: 7ffde000 Unfrozen

ChildEBP RetAddr  Args to Child

WARNING: Stack unwind information not available. Following frames may be wrong.

0012f750 77962148 00000000 00000000 00220000 ntdll!KiFastSystemCallRet

0012f778 7795fc7f 00220138 7793b612 0000003f ntdll!EtwEventEnabled+0xd9

0012f854 77985ae0 00000041 00000050 002201d4 ntdll!RtlFreeThreadActivationContextStack+0x480

0012f8d8 764c9d45 00220000 00000000 00000041 ntdll!wcsnicmp+0x1e4

0012f8f8 74b7ebb5 00000041 0012fdc0 00000000 msvcrt!malloc+0x57

http://blog.csdn.net/magictong/article/details/6304439

线程天敌TerminateThread与SuspendThread的更多相关文章

  1. C++ 线程的创建,挂起,唤醒,终止

    例子: 线程代码: DWORD __stdcall ThreadProc(LPVOID lpParameter) { CMultiThreadDlg * pdlg = (CMultiThreadDlg ...

  2. windows下进程与线程剖析

    进程与线程的解析 进程:一个正在运行的程序的实例,由两部分组成: 1.一个内核对象,操作系统用它来管理进程.内核对象也是系统保存进程统计信息的地方. 2.一个地址空间,其中包含所有可执行文件或DLL模 ...

  3. MFC【17-1】线程和线程同步化

    17.1线程 对于Windows来说所有的线程都是一样的,但MFC却把线程区分为两种类型:User Interface(UI) threads(用户界面(UI)线程)和Worker threads(工 ...

  4. Windows编程之线程

    本笔记整理自:<Windows核心编程(第五版)> 目录 何为线程 线程的开始和结束 创建线程 终止线程 线程运行时的调度和线程优先级 挂起(暂停).恢复与睡眠 挂起 恢复 睡眠 线程切换 ...

  5. windows核心编程---第五章 线程的基础

    与前面介绍的进程一样,线程也有两部分组成.一个是线程内核对象.它是一个数据结构,操作系统用它来管理线程以及用它来存储线程的一些统计信息.另一个是线程栈,用于维护线程执行时所需的所有函数参数和局部变量. ...

  6. C/C++四种退出线程的方法

    退出线程可以有四种方法: 1.线程函数的return返回(最好这样): 其中用线程函数的return返回, 而终止线程是最安全的, 在线程函数return返回后, 会清理函数内申请的类对象, 即调用这 ...

  7. CloseHandle(),TerminateThread(),ExitThread()的区别

    线程的handle用处:线程的handle是指向“线程的内核对象”的,而不是指向线程本身.每个内核对象只是内核分配的一个内存块,并且只能由内核访问.该内存块是一种数据结构,它的成员负责维护对象的各种信 ...

  8. CloseHandle(),TerminateThread(),ExitThread()的差别

    线程的handle用处: 线程的handle是指向"线程的内核对象"的,而不是指向线程本身.每一个内核对象仅仅是内核分配的一个内存块,而且仅仅能由内核訪问.该内存块是一种数据结构, ...

  9. windows自带的线程池

    #define _CRT_SECURE_NO_WARNINGS #include "iostream" #include "windows.h" using n ...

随机推荐

  1. 微信小程序开发demo-地图定位

    要求要完成的功能: 1.要完成的要点是城市定位. 2.就是切换城市. 首页我们先参照微信小程序开放的官方文档找到: 在这里我们可以找到”当前位置经纬度“ getLocation: function ( ...

  2. 利用navicat写mysql的存储过程

    最近项目经理让我给新的活动的预留一个插入红包和查看详情的sql,方便在项目出问题的做一些紧急操作,我想了下这里面还涉及到挺多逻辑和挺多表的一句句查也不方便啊,干脆写到存储过程里,于是开始在navica ...

  3. jquery.uploadify上传图片,点击保存按钮无法使用解决方法

    用Chrome浏览器上传商品图片时,保存按钮无法点击,如下图 原因:Flash插件状态为禁止 或 询问(默认) 解决方法:将Flash插件状态改为允许,如下图

  4. C# 使用外部别名

    原文:C# 使用外部别名 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://lindexi.gitee.io 访问博客.本文 ...

  5. Android系统联系人全特效实现(下),字母表快速滚动

    在上一篇文章中,我和大家一起实现了类似于Android系统联系人的分组导航和挤压动画功能,不过既然文章名叫做<Android系统联系人全特效实现>,那么没有快速滚动功能显然是称不上&quo ...

  6. WinForm和WPF颜色对象的转换

    原文:WinForm和WPF颜色对象的转换 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/huangli321456/article/details ...

  7. mysql常见操作汇总 专题

    mysql中in多个字段 1. 基本用法 SELECT * FROM USER WHERE , , ); 2. 多个字段同时使用 SELECT * FROM USER WHERE (, ),(, ), ...

  8. WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画

    原文:WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画 利用WPF的ListView控件实现类似于Winform中DataGrid行背景色交替变换的效果,同 ...

  9. Emgu-WPF 激光雷达研究-移动物体检测

    原文:Emgu-WPF 激光雷达研究-移动物体检测 接上篇: https://blog.csdn.net/u013224722/article/details/80738619 先pose出效果图,下 ...

  10. Mysql事务,并发问题,锁机制-- 幻读、不可重复读--专题

    1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约 ...