MFC 线程的退出方法
A.線程函數的返回(推薦用法)(需要考慮的是catch/運行標志/錯誤處理等方法)
B.ExitThread函數(不推薦)
C.同一個進程或者另一個進程中的線程調用TerminateThread函數(應該避免這種方法)
D.包含線程的進程終止(應該避免使用這種方法)
?
線程函數的返回可以確保線程中的C++對象被撤銷函數正確的撤銷,操作系統正確的釋放線程堆棧所使用的內存,系統將線程的退出代碼設置為線程函數的返回值,系統將遞減線程內核對象的使用計數。而ExitThread函數會促使系統清除該線程所使用的所有操作系統資源,但是C++對象等資源將不被撤銷(比如new變量),因此最好從線程函數返回。TerminateThread是異步運行函數,它告訴系統希望線程終止,但不保證線程撤銷的操作結果。如果需要確切的知道線程是否已經終止運行,需要調用WaitForSingleObject或者類似的函數,傳遞線程的句柄(Learned@_@)異步的使用方法造成在擁有線程的進程終止運行之前系統不撤銷改線程的堆棧,這給其他依然在執行的線程留下了使用數據的機會。但是除非必要該函數不推薦使用。
BOOL GetExitCodeThread(
HANDLE hThread,
PDWORD pdwExitCode);用于檢查hThread標志的線程是否終止運行,如果未終止,該函數使用STILL_ACTIVE標識符填入DWORD,如果已經運行成功則返回TRUE。
?
使用線程退出的幾種方法:
(1):
我想大部分人為了圖方便,會定義一個BOOL變量如: BOOL?g_bExtiThread ; 當 if( g_bExtiThread ?== 0?)的時候跳出線程循環,結束線程;
既:g_bExtiThread = 0;只做這一步,會隱藏一個問題, 如果線程執行的時間較長,如循環中Sleep(1000); ?這樣會導致,執行?g_bExtiThread = 0; 后立即執行后面的函數,而不會等待線程結束;如果線程中的變量與g_bExtiThread = 0;后面的執行相關,就可能隱藏問題;
?
(2):
這里自然,有人會用一個簡單的方法避免這個過程就是:
?
?g_bExtiThread = 0;
Sleep(2000);
?
這樣, 等待線程結束后,執行其他語句;
這樣做,有兩個問題:1.效率上比較低,因為即使1000毫秒結束了,可是,卻要等待Sleep(2000);?
?2.如果上述修改為:
?
?
?g_bExtiThread = 0;
Sleep(1001);
這樣看著不錯,但是,如果線程中,Sleep (1000),軟后語句執行的時間,大于1毫秒,也就是說 線程循環一次的時間 大于1001毫秒,仍然可能導致 “(1)”中的問題;或者,線程很多,當線程執行到某一段的時候, CPU的時間片分配給其他線程,這樣,依然會導致 線程中的循環時間無法確定;想通過Sleep的方法等待線程循環結束,只有將時間給的很長,但是這樣太浪費時間和效率了。
?
(3):
比較通用的方法:
通過WaitForSingleObject獲取線程狀態,如果線程退出,執行后面的語句;
?
?g_bExtiThread = 0;
WaitForSingleObject( pWinThreadtestexit->m_hThread, INFINITE );
示例:
?
?UINT thread_testexit( PVOID pParam )
{
while( g_bExtiThread )
{
Sleep(1000);
static int i = 0;
CString str;str.Format( L"%d",i++);
//AfxGetApp()->GetMainWnd()->SetWindowText( str );
}
return 0;
}
void Ctmfc1Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知處理程序代碼
pWinThreadtestexit = AfxBeginThread( thread_testexit, 0 );
}
void Ctmfc1Dlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知處理程序代碼
g_bExtiThread = 0;
WaitForSingleObject( pWinThreadtestexit->m_hThread, INFINITE );
SetWindowText( L"線程已經停止" );
}
?
(4):
這里我打算講解一下在程序退出時,退出所有線程的一個更加通用的方法。道理上將,進程退出,進程中相關資源全部釋放,包括線程,但是,大部分多線程情況,如果線程沒有退出,會導致程序退出的時候崩潰,所以強烈建議所有線程都要退出。
方法:
//工作中線程:可以有多個相同或不同線程;這里一個舉例;
?
?bool bIsThreadWorking = 1;
bool bIsThreadStop = 0;
uint WorkThread( void * pParam )
{
while( bIsThreadWorking )
{
sleep(1000);
//......
}
bIsThreadStop = 1;//到這里表示已經跳出循環了,之后執行 return 0,退出線程;
return 0;
}
//這個線程負責監控退出這個程序;
uint ExitApplicationThread( void * pParam )
{
while(1)
{
if( bIsThreadStop == 1 )
{
breadk;
}
sleep(10); //
}
sleep(100);//最好寫上,確實等到別的線程退出;
exit( 0 ); // 或者通知主程序退出,如 ::postMessage( afxGetApp()->m_pMainWind->m_hWnd, WM_CLOSE, 0, 0 );
return 0;
}
//主線程:
void CMainDlg::ButtonExitApplication()
{
bIsThreadWorking = 1;
AfxBeginThread( ExitApplicationThread, 0 );
// HANDLE hThread = ::CreateThread( 0, 0, ExitApplicationThread, 0, NULL, NULL );
}
有人會疑問,為什么不直接將ExitApplicationThread中的代碼寫到CMainDlg::ButtonExitApplication()中,而是要重新啟動一個線程呢?
解答:
?
? ? //注意:在windows上,AfxBeginThread?和?CreateThread?創建的線程是有一點區別的;
? ?//AfxBeginThread創建的線程,主線程的sleep函數可以阻塞AfxBeginThread創建的線程,也可以理解為只要主線程執行,AfxBeginThread 創建的線程就沒有機會執行,而且不僅僅是優先級的問題,而是MFC設計的問題;AfxBeginThread 創建的線程之間有相同的執行機會;而CreateThread創建的線程才可以和主線程一樣,不論哪個線程執行,主線程和工作線程都有機會得到執行機會;?如果用CreateThread創建線程,就可以再主線程直接判斷執行了;這個方法是比較好的方法,其實質和信號量的方法退出線程是一樣的;如果是多線程的話,可以用鏈表將線程退出變量保存,在退出循環中判斷,直到所有線程退出變量bIsThreadStop?為真后再跳出ExitApplicationThread線程循環,結束主線程;
?
(5):
以下windows上退出線程的方法不推薦,可以了解;
?
?BOOL TerminateThread(
HANDLE hThread, // handle to thread
DWORD dwExitCode // exit code
);
這里推薦一篇文章:
?
?
TerminateThread?is a dangerous function that should only be used in the most extreme cases. You should call?TerminateThread?only if you know exactly what the target?thread?is doing, and you control all of the code that the target?thread?could possibly be running at the time of the termination. For example,?TerminateThread?can result in the following problems:
- If the target?thread?owns a critical section, the critical section will not be released.(未釋放互斥區,造成死鎖)
- If the target?thread?is allocating memory from the heap, the heap lock will not be released.(未釋放堆分配鎖,造成死鎖)
- If the target?thread?is executing certain kernel32 calls when it is terminated, the kernel32 state for the?thread’s process could be inconsistent.(在執行內核函數時退出,造成該線程所在進程狀態不確定,程序可能崩潰)
- 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得程序可能出現問題!)
A?thread?cannot protect itself against?TerminateThread, other than by controlling access to its handles. The?thread?handle returned by the?CreateThread?and?CreateProcess?functions has?THREAD_TERMINATE?access, so any caller holding one of these handles can?terminate?your?thread.?
?
TerminateThread的方法不推薦使用
聽過無數次不要TerminateThread,只是工作中常用,貌似也沒有什么問題。今天在高強度測試中發現了一個不可原諒的錯誤。參看下面的例子
?
?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,作為網絡不通時的退出機制,以后要改改了。比如讓該線程自生自滅,自行退出。
再推薦一篇文章:
?
CloseHandle(),TerminateThread(),ExitThread()的區別
?
?
線程的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獲得返回的值。
總結
以上是生活随笔為你收集整理的MFC 线程的退出方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 研发管理:产品研发团队的早会
- 下一篇: 如何修改MFC的图标