MFC 线程的退出方法
A.線程函數(shù)的返回(推薦用法)(需要考慮的是catch/運行標(biāo)志/錯誤處理等方法)
B.ExitThread函數(shù)(不推薦)
C.同一個進(jìn)程或者另一個進(jìn)程中的線程調(diào)用TerminateThread函數(shù)(應(yīng)該避免這種方法)
D.包含線程的進(jìn)程終止(應(yīng)該避免使用這種方法)
?
線程函數(shù)的返回可以確保線程中的C++對象被撤銷函數(shù)正確的撤銷,操作系統(tǒng)正確的釋放線程堆棧所使用的內(nèi)存,系統(tǒng)將線程的退出代碼設(shè)置為線程函數(shù)的返回值,系統(tǒng)將遞減線程內(nèi)核對象的使用計數(shù)。而ExitThread函數(shù)會促使系統(tǒng)清除該線程所使用的所有操作系統(tǒng)資源,但是C++對象等資源將不被撤銷(比如new變量),因此最好從線程函數(shù)返回。TerminateThread是異步運行函數(shù),它告訴系統(tǒng)希望線程終止,但不保證線程撤銷的操作結(jié)果。如果需要確切的知道線程是否已經(jīng)終止運行,需要調(diào)用WaitForSingleObject或者類似的函數(shù),傳遞線程的句柄(Learned@_@)異步的使用方法造成在擁有線程的進(jìn)程終止運行之前系統(tǒng)不撤銷改線程的堆棧,這給其他依然在執(zhí)行的線程留下了使用數(shù)據(jù)的機會。但是除非必要該函數(shù)不推薦使用。
BOOL GetExitCodeThread(
HANDLE hThread,
PDWORD pdwExitCode);用于檢查hThread標(biāo)志的線程是否終止運行,如果未終止,該函數(shù)使用STILL_ACTIVE標(biāo)識符填入DWORD,如果已經(jīng)運行成功則返回TRUE。
?
使用線程退出的幾種方法:
(1):
我想大部分人為了圖方便,會定義一個BOOL變量如: BOOL?g_bExtiThread ; 當(dāng) if( g_bExtiThread ?== 0?)的時候跳出線程循環(huán),結(jié)束線程;
既:g_bExtiThread = 0;只做這一步,會隱藏一個問題, 如果線程執(zhí)行的時間較長,如循環(huán)中Sleep(1000); ?這樣會導(dǎo)致,執(zhí)行?g_bExtiThread = 0; 后立即執(zhí)行后面的函數(shù),而不會等待線程結(jié)束;如果線程中的變量與g_bExtiThread = 0;后面的執(zhí)行相關(guān),就可能隱藏問題;
?
(2):
這里自然,有人會用一個簡單的方法避免這個過程就是:
?
?g_bExtiThread = 0;
Sleep(2000);
?
這樣, 等待線程結(jié)束后,執(zhí)行其他語句;
這樣做,有兩個問題:1.效率上比較低,因為即使1000毫秒結(jié)束了,可是,卻要等待Sleep(2000);?
?2.如果上述修改為:
?
?
?g_bExtiThread = 0;
Sleep(1001);
這樣看著不錯,但是,如果線程中,Sleep (1000),軟后語句執(zhí)行的時間,大于1毫秒,也就是說 線程循環(huán)一次的時間 大于1001毫秒,仍然可能導(dǎo)致 “(1)”中的問題;或者,線程很多,當(dāng)線程執(zhí)行到某一段的時候, CPU的時間片分配給其他線程,這樣,依然會導(dǎo)致 線程中的循環(huán)時間無法確定;想通過Sleep的方法等待線程循環(huán)結(jié)束,只有將時間給的很長,但是這樣太浪費時間和效率了。
?
(3):
比較通用的方法:
通過WaitForSingleObject獲取線程狀態(tài),如果線程退出,執(zhí)行后面的語句;
?
?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"線程已經(jīng)停止" );
}
?
(4):
這里我打算講解一下在程序退出時,退出所有線程的一個更加通用的方法。道理上將,進(jìn)程退出,進(jìn)程中相關(guān)資源全部釋放,包括線程,但是,大部分多線程情況,如果線程沒有退出,會導(dǎo)致程序退出的時候崩潰,所以強烈建議所有線程都要退出。
方法:
//工作中線程:可以有多個相同或不同線程;這里一個舉例;
?
?bool bIsThreadWorking = 1;
bool bIsThreadStop = 0;
uint WorkThread( void * pParam )
{
while( bIsThreadWorking )
{
sleep(1000);
//......
}
bIsThreadStop = 1;//到這里表示已經(jīng)跳出循環(huán)了,之后執(zhí)行 return 0,退出線程;
return 0;
}
//這個線程負(fù)責(zé)監(jiān)控退出這個程序;
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?創(chuàng)建的線程是有一點區(qū)別的;
? ?//AfxBeginThread創(chuàng)建的線程,主線程的sleep函數(shù)可以阻塞AfxBeginThread創(chuàng)建的線程,也可以理解為只要主線程執(zhí)行,AfxBeginThread 創(chuàng)建的線程就沒有機會執(zhí)行,而且不僅僅是優(yōu)先級的問題,而是MFC設(shè)計的問題;AfxBeginThread 創(chuàng)建的線程之間有相同的執(zhí)行機會;而CreateThread創(chuàng)建的線程才可以和主線程一樣,不論哪個線程執(zhí)行,主線程和工作線程都有機會得到執(zhí)行機會;?如果用CreateThread創(chuàng)建線程,就可以再主線程直接判斷執(zhí)行了;這個方法是比較好的方法,其實質(zhì)和信號量的方法退出線程是一樣的;如果是多線程的話,可以用鏈表將線程退出變量保存,在退出循環(huán)中判斷,直到所有線程退出變量bIsThreadStop?為真后再跳出ExitApplicationThread線程循環(huán),結(jié)束主線程;
?
(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.(未釋放互斥區(qū),造成死鎖)
- 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.(在執(zhí)行內(nèi)核函數(shù)時退出,造成該線程所在進(jìn)程狀態(tài)不確定,程序可能崩潰)
- 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得程序可能出現(xiàn)問題!)
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的方法不推薦使用
聽過無數(shù)次不要TerminateThread,只是工作中常用,貌似也沒有什么問題。今天在高強度測試中發(fā)現(xiàn)了一個不可原諒的錯誤。參看下面的例子
?
?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操作符用的是小塊堆,整個進(jìn)程在分配和回收內(nèi)存時,都要用同一把鎖。如果一個線程在占用該鎖時被殺死(即臨死前該線程在new或delete操作中),其他線程就無法再使用new或delete了,表現(xiàn)為hang住。
《核心編程》里明確提醒不要TerminateThread,但原因并不是血淋淋滴。今天發(fā)現(xiàn)的這個bug印證了此書的價值。
另注:許多臨時的網(wǎng)絡(luò)操作經(jīng)常用TerminateThread,作為網(wǎng)絡(luò)不通時的退出機制,以后要改改了。比如讓該線程自生自滅,自行退出。
再推薦一篇文章:
?
CloseHandle(),TerminateThread(),ExitThread()的區(qū)別
?
?
線程的handle用處:
線程的handle是指向“線程的內(nèi)核對象”的,而不是指向線程本身.每個內(nèi)核對象只是內(nèi)核分配的一個內(nèi)存塊,并且只能由內(nèi)核訪問。該內(nèi)存塊是一種數(shù)據(jù)結(jié)構(gòu),它的成員負(fù)責(zé)維護(hù)對象的各種信息(eg: 安全性描述,引用計數(shù)等)。
?
CloseHandle()
在CreateThread成功之后會返回一個hThread的handle,且內(nèi)核對象的計數(shù)加1,CloseHandle之后,引用計數(shù)減1,當(dāng)變?yōu)?時,系統(tǒng)刪除內(nèi)核對象。
但是這個handle并不能完全代表這個線程,它僅僅是線程的一個“標(biāo)識”,系統(tǒng)和用戶可以利用它對相應(yīng)的線程進(jìn)行必要的操縱。如果在線程成功創(chuàng)建后,不再需要用到這個句柄,就可以在創(chuàng)建成功后,線程退出前直接CloseHandle掉,但這并不會影響到線程的運行。
?
不執(zhí)行CloseHandle() 帶來的后果:
若在線程執(zhí)行完之后,沒有通過CloseHandle()將引用計數(shù)減1,在進(jìn)程執(zhí)行期間,將會造成內(nèi)核對象的泄露,相當(dāng)與句柄泄露,但不同于內(nèi)存泄露, 這勢必會對系統(tǒng)的效率帶來一定程度上的負(fù)面影響。但是,請記住,當(dāng)進(jìn)程結(jié)束退出后,系統(tǒng)仍然會自動幫你清理這些資源。但是在這里不推薦這種做法,畢竟不是 一個良好的編程習(xí)慣!
( 應(yīng)用程序運行時,有可能泄露內(nèi)核對象,但是當(dāng)進(jìn)程終止運行時,系統(tǒng)能確保所有內(nèi)容均被正確地清除。另外,這個情況是用于所有對象,資源和內(nèi)存塊,也就是說,當(dāng)進(jìn)程終止時,系統(tǒng)將保證不會留下任何對象。)
?
TerminateThread()
函數(shù)的聲明如下:
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);作用:在線程外終止一個線程,用于強制終止線程。參數(shù)說明:HANDLE htread:被終止的線程的句柄,為CWinThread指針。DWORD dwExitCode:退出碼。返回值:函數(shù)執(zhí)行成功則返回非零值,執(zhí)行失敗返回0。調(diào)用getlasterror獲得返回的值。
總結(jié)
以上是生活随笔為你收集整理的MFC 线程的退出方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 研发管理:产品研发团队的早会
- 下一篇: 如何修改MFC的图标