線程天敵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