C/C++線程退出的四種方法小結(jié)
想要終止線程的運(yùn)行,可以使用以下方法:
- 線程函數(shù)返回(最好使用該方法)。
- 同一個進(jìn)程或另一個進(jìn)程中的線程調(diào)用TerminateThread函數(shù)(應(yīng)避免使用該方法)。
- 通過調(diào)用ExitThread函數(shù),線程將自行撤消(最好不使用該方法)。
- ExitProcess和TerminateProcess函數(shù)也可以用來終止線程的運(yùn)行(應(yīng)避免使用該方法)。
下面將詳細(xì)介紹終止線程運(yùn)行的方法:1-4,并說明線程終止運(yùn)行時會出現(xiàn)何種情況:5。
為了說明以下線程的退出時發(fā)生的動作,引入以下測試代碼:
class ?TTThread { public: ? ? /** @name Constructors and Destructor*/ ? ? TTThread(); ? ? virtual ~TTThread(); public: ?? ?//線程創(chuàng)建函數(shù) ?? ?BOOL Create(); ?? ?//線程銷毀,在這里驗(yàn)證TerminateThread ?? ?void Destory(); ?? ?//線程等待 ?? ?BOOL Wait(DWORD dwWaitTime); ?? ?inline DWORD GetThreadID() { return m_dwThreadID; } protected: ?? ?//線程函數(shù)邏輯接口,子類繼承 ?? ?//在這里驗(yàn)證ExitThread和正常退出操作 ?? ?virtual unsigned Process(); private: ?? ?//線程入口地址 ?? ?static unsigned ?__stdcall _threadProc(void *lpParam); private: ?? ?HANDLE?? ??? ?m_hThread; ?? ?DWORD?? ??? ?m_dwThreadID; }; //C++對象 class CObj { public: ? ? CObj() ? ? { ? ? ? printf("CObj create...\n"); ? ? } ? ? ~CObj() ? ? { ? ? ? ? printf("CObj delete...\n"); ? ? } }; //線程測試類 class CTestThread : public TTThread { public: ? ? virtual unsigned Process() ? ? { ? ? ? ?return 0; ? ? } };
線程函數(shù)返回
始終都應(yīng)該將線程設(shè)計(jì)成這樣的形式,即當(dāng)想要線程終止運(yùn)行時,它們就能夠返回。這是確保所有線程資源被正確地清除的唯一辦法。
如果線程能夠返回,就可以確保下列事項(xiàng)的實(shí)現(xiàn):
(1)在線程函數(shù)中創(chuàng)建的所有C++對象均將通過它們的析構(gòu)函數(shù)進(jìn)行釋放。
(2)操作系統(tǒng)將正確地釋放線程堆棧使用的內(nèi)存。
(3)系統(tǒng)將線程的退出代碼(在線程的內(nèi)核對象中維護(hù))設(shè)置為線程函數(shù)的返回值。
(4)系統(tǒng)將遞減線程內(nèi)核對象的使用計(jì)數(shù)。
其中,(1)(2)兩點(diǎn)在編碼中需要特別關(guān)注的,這個在編碼規(guī)范上比較重要。(jimmy注)
測試代碼:
int _tmain(int argc, _TCHAR* argv[]) { ? ? printf("main thread begin...\n"); ? ? //創(chuàng)建線程 ? ? CTestThread ?workthread; ? ? workthread.Create(); ? ? //等待線程結(jié)束,此時不會執(zhí)行Destroy函數(shù),線程正常結(jié)束 ? ? if (!workthread.Wait(3 * 1000)) ? ? { ? ? ? ? //超時銷毀線程 ? ? ?? ? ? ? ? workthread.Destory(); ? ? } ? ? printf("main thread end...\n"); ? ? return 0; }
測試結(jié)果:
main thread begin...
CObj create...//c++對象創(chuàng)建
current index:0
current index:1
current index:2
current index:3
current index:4
CObj delete...//C++對象析構(gòu)以及線程申請的內(nèi)存正確釋放
main thread end...
請按任意鍵繼續(xù). . .
TerminateThread函數(shù)
調(diào)用TerminateThread函數(shù)也能夠終止線程的運(yùn)行,其函數(shù)原型如下:
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode);
TerminateThread能夠撤消任何線程,其中hThread參數(shù)用于標(biāo)識被終止運(yùn)行的線程的句柄。當(dāng)線程終止運(yùn)行時,它的退出代碼成為你作為dwExitCode參數(shù)傳遞的值。同時,線程的內(nèi)核對象的使用計(jì)數(shù)也被遞減。
注意TerminateThread函數(shù)是異步運(yùn)行的函數(shù),也就是說,它告訴系統(tǒng)你想要線程終止運(yùn)行,但是,當(dāng)函數(shù)返回時,不能保證線程被撤消。如果需要確切地知道該線程已經(jīng)終止運(yùn)行,必須調(diào)用WaitForSingleObject或者類似的函數(shù),傳遞線程的句柄。
用TerminateThread銷毀線程示例代碼:
// 在另一個線程中進(jìn)行調(diào)用,這里是在主線程終止測試線程 void TTThread::Destory() { if (m_hThread) { //TerminateThread以異步方式執(zhí)行,函數(shù)返回不代表線程結(jié)束, //線程函數(shù)停止執(zhí)行,位置隨機(jī),類對象不會被析構(gòu)導(dǎo)致內(nèi)存泄漏 ::TerminateThread(m_hThread, 0); //等待線程結(jié)束 ::WaitForSingleObject(m_hThread, 500); ::CloseHandle(m_hThread); m_hThread = 0; m_dwThreadID = 0; printf("end destroy\n"); } }
設(shè)計(jì)良好的應(yīng)用程序從來不使用這個函數(shù),因?yàn)楸唤K止運(yùn)行的線程收不到它被撤消的通知。并且,如果使用TerminateThread,那么在擁有線程的進(jìn)程終止運(yùn)行之前,系統(tǒng)不撤消該線程的堆棧,造成內(nèi)存不能及時釋放。
int _tmain(int argc, _TCHAR* argv[]) { ? ? printf("main thread begin...\n"); ? ? //創(chuàng)建線程 ? ? CTestThread ?workthread; ? ? workthread.Create(); ? ? //等待線程結(jié)束,此時會執(zhí)行Destroy函數(shù),線程異常結(jié)束 ? ? if (!workthread.Wait(1 * 1000)) ? ? { ? ? ? ? //超時,主線程銷毀測試線程 ? ? ?? ? ? ? ? workthread.Destory(); ? ? } ? ? printf("main thread end...\n"); ? ? return 0; }
測試結(jié)果
main thread begin...
CObj create...
current index:0
current index:1
end destroy
main thread end...
//CObj沒有執(zhí)行析構(gòu)函數(shù),沒有及時釋放線程申請的_tiddata內(nèi)存
請按任意鍵繼續(xù). . .
ExitThread函數(shù)
可以讓線程調(diào)用ExitThread函數(shù),以便強(qiáng)制終止當(dāng)前線程運(yùn)行,其函數(shù)原型:
VOID ExitThread(DWORD dwExitCode);
該函數(shù)將終止線程的運(yùn)行,并導(dǎo)致操作系統(tǒng)清除該線程使用的所有操作系統(tǒng)資源。但是,C++資源(如C++類對象)將不被析構(gòu)。由于這個原因,最好從線程函數(shù)返回,而不是通過調(diào)用ExitThread來返回。
當(dāng)然,可以使用ExitThread的dwExitThread參數(shù)告訴系統(tǒng)將線程的退出代碼設(shè)置為什么。ExitThread函數(shù)并不返回任何值,因?yàn)榫€程已經(jīng)終止運(yùn)行,不能執(zhí)行更多的代碼。
注意終止線程運(yùn)行的最佳方法是讓它的線程函數(shù)返回。但是,如果使用本節(jié)介紹的方法,應(yīng)該知道ExitThread函數(shù)是Windows用來撤消線程的函數(shù)。如果編寫C/C++代碼,那么決不應(yīng)該調(diào)用ExitThread。應(yīng)該使用Visual C++運(yùn)行期庫函數(shù)_endthreadex,因?yàn)開endthreadex可以確保,及時釋放線程申請的tiddata內(nèi)存。
測試代碼
class CTestThread : public TTThread { public: ? ? virtual unsigned Process() ? ? { ? ? ? ? CObj obj; ? ? ? ?for (int i = 0; i < 5; i++) ? ? ? ?{ ? ? ? ? ? ?Sleep(400); ? ? ? ? ? ?printf("current index:%d\n", i); ? ? ? ? ? ?//當(dāng)前線程退出 ? ? ? ? ? ?if (i == 3) ? ? ? ? ? ?{ ? ? ? ? ? ? ? ?printf("make thread end...\n"); ? ? ? ? ? ? ? ?//_endthreadex(0); ? ? ? ? ? ? ? ?ExitThread(0); ? ? ? ? ? ?} ? ? ? ?} ? ? ? ?return 0; ? ? } }; int _tmain(int argc, _TCHAR* argv[]) { ? ? printf("main thread begin...\n"); ? ? //創(chuàng)建線程 ? ? CTestThread ?workthread; ? ? workthread.Create(); ?? ?//等待線程結(jié)束 ? ? if (workthread.Wait(3 * 1000)) ? ? { ? ? ? ? printf("the test thread end..."); ? ? } ? ? printf("main thread end...\n"); ? ? return 0; }
測試結(jié)果
main thread begin...
CObj create...
current index:0
current index:1
current index:2
current index:3
make thread end...線程被中斷執(zhí)行,造成tiddata和CObj內(nèi)存泄漏
the test thread end...main thread end...
請按任意鍵繼續(xù). . .
在進(jìn)程終止運(yùn)行時撤消線程
ExitProcess和TerminateProcess函數(shù)也可以用來終止線程的運(yùn)行。差別在于這些線程將會使終止運(yùn)行的進(jìn)程中的所有線程全部終止運(yùn)行。另外,由于整個進(jìn)程已經(jīng)被關(guān)閉,進(jìn)程使用的所有資源肯定已被清除。這當(dāng)然包括所有線程的堆棧。這兩個函數(shù)會導(dǎo)致進(jìn)程中的剩余線程被強(qiáng)制撤消,就像從每個剩余的線程調(diào)用TerminateThread一樣。顯然,這意味著正確的應(yīng)用程序清除沒有發(fā)生,即C++對象撤消函數(shù)沒有被調(diào)用,數(shù)據(jù)沒有轉(zhuǎn)至磁盤等等。
線程終止運(yùn)行時發(fā)生的操作
當(dāng)線程終止運(yùn)行時,會發(fā)生下列操作:
(1)線程擁有的所有用戶對象均被釋放。在Windows中,大多數(shù)對象是由包含創(chuàng)建這些對象的線程的進(jìn)程擁有的。但是一個線程擁有兩個用戶對象,即窗口和掛鉤。當(dāng)線程終止運(yùn)行時,系統(tǒng)會自動撤消任何窗口,并且卸載線程創(chuàng)建的或安裝的任何掛鉤。其他對象只有在擁有線程的進(jìn)程終止運(yùn)行時才被撤消。
(2)線程的退出代碼從STILL_ACTIVE改為傳遞給ExitThread或TerminateThread的代碼。
(3)線程內(nèi)核對象的狀態(tài)變?yōu)橐淹ㄖ?br />(4)如果線程是進(jìn)程中最后一個活動線程,系統(tǒng)也將進(jìn)程視為已經(jīng)終止運(yùn)行。
(5)線程內(nèi)核對象的使用計(jì)數(shù)遞減1。
當(dāng)一個線程終止運(yùn)行時,在與它相關(guān)聯(lián)的線程內(nèi)核對象的所有未結(jié)束的引用關(guān)閉之前,該內(nèi)核對象不會自動被釋放。
一旦線程不再運(yùn)行,系統(tǒng)中就沒有別的線程能夠處理該線程的句柄。然而別的線程可以調(diào)用GetExitcodeThread來檢查由hThread標(biāo)識的線程是否已經(jīng)終止運(yùn)行。如果它已經(jīng)終止運(yùn)行,則確定它的退出代碼:
函數(shù)原型:
BOOL GetExitCodeThread( HANDLE hThread, PDWORD pdwExitCode);
退出代碼的值在pdwExitCode指向的DWORD中返回。如果調(diào)用GetExitCodeThread時線程尚未終止運(yùn)行,該函數(shù)就用STILL_ACTIVE標(biāo)識符(定義為0x103)填入DWORD。如果該函數(shù)運(yùn)行成功,便返回TRUE。
線程基類整體實(shí)現(xiàn):
/*******************************************************? ?* ?@file ? ? ?TTThread.cpp ?* ?@author ? ?jimmy ?* ?@brief ? windows線程處理的包裝 ?******************************************************/ #include "stdafx.h" #include "TTThread.h" TTThread::TTThread() :m_hThread(0) { } TTThread::~TTThread() { ?? ?if (m_hThread) ? ? { ? ? ? ? ::CloseHandle(m_hThread); ? ? } ?? ?m_hThread = 0; } /************************************************************** * ?@brief : TTThread::create * ? ?? * ?@param : -none * ? ?? * ?@return : BOOL * ? ?? * ?@author : Jimmy * ? ?? * ?@date : 2019/2/13 星期三 * ? ?? * ?@note : 線程創(chuàng)建函數(shù) ***************************************************************/ BOOL TTThread::Create() { ? ? m_hThread = (HANDLE)_beginthreadex(0,0, _threadProc, this, 0, (unsigned*)&m_dwThreadID); ? ? if (0 == m_hThread) ? ? { ? ? ? ? m_dwThreadID = 0; ? ? } ?? ?return m_hThread >(HANDLE)1; } void TTThread::Destory() { ?? ?if (m_hThread) ?? ?{ ?? ??? ?//TerminateThread以異步方式執(zhí)行,函數(shù)返回不代表線程結(jié)束, ? ? ? ? //線程函數(shù)停止執(zhí)行,位置隨機(jī),類對象不會被析構(gòu)導(dǎo)致內(nèi)存泄漏 ? ? ? ? ::TerminateThread(m_hThread, 0); ? ? ? ? //等待線程結(jié)束 ? ? ? ? ::WaitForSingleObject(m_hThread, 500); ?? ??? ?::CloseHandle(m_hThread); ?? ??? ?m_hThread = 0; ?? ??? ?m_dwThreadID = 0; ? ? ? ? printf("end destroy\n"); ?? ?} } BOOL TTThread::Wait(DWORD dwWaitTime) { ?? ?if (m_hThread == 0) ?? ??? ?return TRUE; ?? ?return (::WaitForSingleObject(m_hThread, dwWaitTime) != WAIT_TIMEOUT); } unsigned TTThread::Process() { ?? ?return 0; } unsigned __stdcall TTThread::_threadProc(void *lpParam) { ?? ?TTThread* pThread = (TTThread*)lpParam; ?? ?assert(pThread); ?? ?if (pThread != 0) ?? ?{ ?? ??? ?pThread->Process(); ?? ?} ?? ?return 0; }
參考文章:
https://www.cnblogs.com/arsblog/p/4829729.html
完整線程類代碼參見github:
https://github.com/jinxiang1224/cpp/tree/master/thread
到此這篇關(guān)于C/C++線程退出的四種方法小結(jié)的文章就介紹到這了,更多相關(guān)C/C++線程退出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++面試題之?dāng)?shù)a、b的值互換(不使用中間變量)
這篇文章主要介紹了不使用中間變量,C++實(shí)現(xiàn)數(shù)a、b的值互相轉(zhuǎn)換操作,感興趣的小伙伴們可以參考一下2016-07-07C/C++ 中const關(guān)鍵字的用法小結(jié)
C++中的const關(guān)鍵字的用法非常靈活,而使用const將大大改善程序的健壯性。這篇文章主要介紹了C/C++ 中const關(guān)鍵字的用法,需要的朋友可以參考下2020-02-02C語言詳解分析進(jìn)程控制中進(jìn)程終止的實(shí)現(xiàn)
當(dāng)進(jìn)程完成執(zhí)行最后語句并且通過系統(tǒng)調(diào)用 exit() 請求操作系統(tǒng)刪除自身時,進(jìn)程終止。這時,進(jìn)程可以返回狀態(tài)值(通常為整數(shù))到父進(jìn)程(通過系統(tǒng)調(diào)用 wait())。所有進(jìn)程資源,如物理和虛擬內(nèi)存、打開文件和 I/O 緩沖區(qū)等,會由操作系統(tǒng)釋放2022-08-08C語言實(shí)現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表
這篇文章主要為大家詳細(xì)介紹了c語言實(shí)現(xiàn)通用數(shù)據(jù)結(jié)構(gòu)之通用鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11