標籤:
線程的handle用處:
線程的handle是指向“線程的核心對象”的,而不是指向線程本身.每個核心對象只是核心分配的一個記憶體塊,並且只能由核心訪問。該記憶體塊是一種資料結構,它的成員負責維護對象的各種資訊(eg: 安全性描述,引用計數等)。
CloseHandle()
在CreateThread成功之後會返回一個hThread的handle,且核心對象的計數加1,CloseHandle之後,引用計數減1,當變為0時,系統刪除核心對象。
但是這個handle並不能完全代表這個線程,它僅僅是線程的一個“標識”,系統和使用者可以利用它對相應的線程進行必要的操縱。如果線上程成功建立後,不再需要用到這個控制代碼,就可以在建立成功後,線程退出前直接CloseHandle掉,但這並不會影響到線程的運行。
不執行CloseHandle() 帶來的後果:
若線上程執行完之後,沒有通過CloseHandle()將引用計數減1,在進程執行期間,將會造成核心對象的泄露,相當與控制代碼泄露,但不同於記憶體泄露, 這勢必會對系統的效率帶來一定程度上的負面影響。但是,請記住,當進程結束退出後,系統仍然會自動幫你清理這些資源。但是在這裡不推薦這種做法,畢竟不是 一個良好的編程習慣!
( 應用程式運行時,有可能泄露核心對象,但是當進程終止運行時,系統能確保所有內容均被正確地清除。另外,這個情況是用於所有對象,資源和記憶體塊,也就是說,當進程終止時,系統將保證不會留下任何對象。)
TerminateThread()
函數的聲明如下:
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);
作用:
線上程外終止一個線程,用於強制終止線程。
參數說明:
HANDLE htread:被終止的線程的控制代碼,為CWinThread指標。
DWORD dwExitCode:退出碼。
傳回值:
函數執行成功則返回非零值,執行失敗返回0。調用getlasterror獲得返回的值。
聽過無數次不要TerminateThread,只是工作中常用,貌似也沒有什麼問題。今天在高強度測試中發現了一個不可原諒的錯誤。參看下面的例子
[cpp] view plaincopy
DWORD __stdcall mythread(void*)
{
while( true )
{
char* p = new char[1024];
delete [] p;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE h = CreateThread(NULL, 0, mythread, NULL, 0, NULL);
Sleep(1000);
TerminateThread(h , 0);
h = NULL;
char* p = new char[1024]; // 這裡會死結, 過不去
delete [] p;
return 0;
}
為什麼死結呢?new操作符用的是小塊堆,整個進程在分配和回收記憶體時,都要用同一把鎖。如果一個線程在佔用該鎖時被殺死(即臨死前該線程在new或delete操作中),其他線程就無法再使用new或delete了,表現為hang住。
<核心編程>裡明確提醒不要TerminateThread,但原因並不是血淋淋滴。今天發現的這個bug印證了此書的價值。
另註:許多臨時的網路操作經常用TerminateThread,作為網路不通時的退出機制,以後要改改了。比如讓該線程自生自滅,自行退出。
ExitThread是推薦使用的結束一個線程的方法,當調用該函數時,當前線程的棧被釋放,然後線程終止,相對於TerminateThread函數來說,這樣做能夠更好地完成附加在該線程上的DLL的清除工作
終止線程兩個函數:ExitThread() 和 TerminateThread()
若要終止線程的運行,可以使用下面四種的方法:
線程函數退出迴圈來返回 (最佳方法 )。
通過調用ExitThread 函數,線程將自行撤消(盡量不要使用這種方法 )。
同一個進程或另一個進程中的線程調用TerminateThread 函數(最好避免使用這種方法 )。
該線程的主進程終止運行(避免使用 )。
下面將介紹終止線程啟動並執行方法,並且說明線程終止運行時會出現什麼情況。
1.線程函數返回
始終都應該將線程設計成這樣的形式,即當想要線程終止運行時,它們就能夠返回。這是
確保所有線程資源被正確地清除的唯一辦法。
如果線程能夠返回,就可以確保下列事項的實現:
a) 線上程函數中建立的所有C + +對象均將通過它們的撤消函數正確地撤消。
b)作業系統將正確地釋放線程堆棧使用的記憶體。
c)系統將線程的結束代碼(線上程的核心對象中維護)設定為線程函數的傳回值。
d)系統將遞減線程核心對象的使用計數。
2.ExitThread 函數
可以讓線程調用ExitThread 函數,以便強制線程終止運行:
該函數將終止線程的運行,並導致作業系統清除該線程使用的所有作業系統資源。但是,C++資源(如C++類對象)將不被撤消。由於這個原因,最好從線程函數返回,而不是通過調用ExitThread 來返回。
當然,可以使用ExitThread 的dwExitThread 參數告訴系統將線程的結束代碼設定為什麼。ExitThread 函數並不返回任何值,因為線程已經終止運行,不能執行更多的代碼。
注意終止線程啟動並執行最佳方法是讓它的線程函數返回。但是,如果使用本節介紹的方法,應該知道ExitThread 函數是Windows用來撤消線程的函數。如果編寫C/C++代碼,那麼決不應該調用ExitThread 。應該使用Visual C++運行期庫函數_endthreadex。如果不使用Microsoft的Visual C++編譯器,你的編譯器供應商有它自己的ExitThread 的替代函數。不管這個替代函數是什麼,都必須使用。本章後面將說明_endthreadex的作用和它的重要性。
3.TerminateThread 函數
調用TerminateThread 函數也能夠終止線程的運行:
與ExitThread 不同,ExitThread 總是撤消調用的線程,而TerminateThread 能夠撤消任何線程。hThread參數用於標識被終止啟動並執行線程的控制代碼。當線程終止運行時,它的結束代碼成為你作為dwExitThread 參數傳遞的值。同時,線程的核心對象的使用計數也被遞減。
注意TerminateThread 函數是非同步啟動並執行函數,也就是說,它告訴系統你想要線程終止運行,但是,當函數返回時,不能保證線程被撤消。如果需要確切地知道該線程已經終止運行,必須調用WaitForSingleObject或者類似的函數,傳遞線程的控制代碼。
設計良好的應用程式從來不使用這個函數,因為被終止啟動並執行線程收不到它被撤消的通知。線程不能正確地清除,並且不能防止自己被撤消。注意當使用返回或調用ExitThread 的方法撤消線程時,該線程的記憶體堆棧也被撤消。但是,如果使用TerminateThread ,那麼在擁有線程的進程終止運行之前,系統不撤消該線程的堆棧。Microsoft故意用這種方法來實現TerminateThread 。如果其他仍然正在執行的線程要引用強制撤消的線程堆棧上的值,那麼其他的線程就會出現訪問違規的問題。如果將已經撤消的線程的堆棧留在記憶體中,那麼其他線程就可以繼續很好地運行。此外,當線程終止運行時, DLL通常接收通知。如果使用Terminate Thread 強迫線程終止,DLL就不接收通知,這能阻止適當的清除(詳細資料參見第20章)。
4.在進程終止運行時撤消線程
ExitProcess和TerminateProcess函數也可以用來終止線程的運行。差別在於這些線程將會使終止啟動並執行進程中的所有線程全部終止運行。另外,由於整個進程已經被關閉,進程使用的所有資源肯定已被清除。這當然包括所有線程的堆棧。這兩個函數會導致進程中的剩餘線程被強制撤消,就像從每個剩餘的線程調用TerminateThread 一樣。顯然,這意味著正確的應用程式清除沒有發生,即C++對象撤消函數沒有被調用,資料沒有轉至磁碟等等。
CloseHandle(),TerminateThread(),ExitThread()的區別