VC獲取子執行緒入口函式的退出碼(分別由CreateThread,_beginthread,_beginthreadex與AfxbeginThread 建立的子執行緒 )
在 VC 程式設計中,若是涉及到多執行緒程式設計時,有時我們也需要根據情況獲取子執行緒入口函式的退出碼,以便根據具體的程式執行情況做相應的處理。
之前我在使用 VC 開發程式時,也遇到過需要獲取子執行緒入口函式的退出碼的問題,由於起初沒有做過相似的程式,所以只會定義一個全域性變數來根據情況,在子執行緒的入口函式裡再為其賦予相應的值。雖然這也是一種方法,但根本不能決定根本的問題,而且若程式中的全域性變數定義太多的話又容易造成程式的混亂,但是根據執行緒入口函式的宣告方式,明顯是有返回值的,所以我們可以根據它來在主執行緒直接獲取子執行緒的退出碼。
因此我上網搜尋了一下,網上基本都說可以通過在主 執行緒裡面直接呼叫 GetExitCodeThread() 函式來得到子執行緒入口函式的退出碼。
GetExitCodeThread()這個函式確實可以得到子執行緒入口函式的退出碼,但是真要使用它來得倒子執行緒入口函式的退出碼的話是有條件的。我在網上搜多了許久,都沒有具體說到這點,很零碎,最後我根據自己的實踐,在結合 MSDN 上面的介紹,以及網上的一些資料,終於成功解決的這個問題。以下我就把我的總結歸納一下,以便以後查詢時可以用到,同時,也希望能幫到其他同仁們!
首先,GetExitCodeThread()這個函式可以直接得到由 CreateThread() 建立的子執行緒入口函式的退出碼。以下我舉個測試的例子吧!
先建立一個基於MFC的控制檯工程,這樣比較方便測試。這個不用我來教大家怎麼建立吧,呵呵!
然後在原始檔開頭,可以在 main 函式外面定義一下變數:
HANDLE hThread;
DWORD errCode = 100;
同時宣告子執行緒入口函式:
DWORD WINAPI ThreadFunc( LPVOID lpParameter );
接著在特定位置新增以下程式碼:
好了,現在就是編寫子執行緒入口函數了,程式碼如下:
現在程式碼編輯工作完成了,然我們測試一下吧,測試效果如下所以:
看!效果剛剛好,正好是我們在子執行緒入口函式裡面的退出碼“8”。
以上使用 CreateThread() 函式建立的子執行緒工作的很好,至少能達到我們想要的結果,這是否意味著使用另外三個函式,_beginthread,_beginthreadex與AfxbeginThread也能達到我們需要的效果呢?我們當然希望是,可惜的是,答案是NO,哦,不好意思,_beginthreadex應該可以,讀者可以自己測試一下,可是_beginthread和AfxbeginThread建立的子執行緒就不能那麼順利了。為什麼呢?以下我先說明一下具體的原因,然後再說明如何來解決這個問題。
CreateThread是Windows的API函式,提供作業系統級別的建立執行緒的操作。_beginthread(及_beginthreadex)與AfxBeginThread的底層實現都呼叫了CreateThread函式。這是相似之處,但有幾個區別之處是值得我們注意的。
AfxBeginThread裡面,CreateThread之後,就CloseHandle了;
當您使用 _beginthread 和 _endthread時,不要通過呼叫 Win32 APICloseHandle 顯式關閉執行緒控制代碼,因為_endthread自動關閉執行緒控制代碼;
當您使用_beginthreadex 和 _endthreadex時,必須通過呼叫 Win32 APICloseHandle 關閉執行緒處理, _endthreadex 不關閉執行緒控制代碼。
以上這幾個地方就是我們應該注意的,現在回到主題,為什麼呼叫GetExitCodeThread()不能正常得到AfxBeginThread和 “_beginthread 與_endthread”的退出碼呢,答案就在他們的底層程式碼自動呼叫CloseHandle函式。
以下我引用網上一段對CloseHandle()函式的介紹:
CloseHandle()
在CreateThread成功之後會返回一個hThread的handle,且核心物件的計數加1,CloseHandle之後,引用計數減1,當變為0時,系統刪除核心物件。
但是這個handle並不能完全代表這個執行緒,它僅僅是執行緒的一個“標識”,系統和使用者可以利用它對相應的執行緒進行必要的操縱。如果線上程成功建立後,不再需要用到這個控制代碼,就可以在建立成功後,執行緒退出前直接CloseHandle掉,但這並不會影響到執行緒的執行。
不執行CloseHandle() 帶來的後果:
若線上程執行完之後,沒有通過CloseHandle()將引用計數減1,在程序執行期間,將會造成核心物件的洩露,相當與控制代碼洩露,但不同於記憶體洩露, 這勢必會對系統的效率帶來一定程度上的負面影響。但是,請記住,當程序結束退出後,系統仍然會自動幫你清理這些資源。但是在這裡不推薦這種做法,畢竟不是 一個良好的程式設計習慣!
( 應用程式執行時,有可能洩露核心物件,但是當程序終止執行時,系統能確保所有內容均被正確地清除。另外,這個情況是用於所有物件,資源和記憶體塊,也就是說,當程序終止時,系統將保證不會留下任何物件。)
呵呵,現在知道為什麼了嗎,啊。。。我也說一下我自己的理解吧,例如,當我們使用AfxBeginThread函式建立一個子執行緒時,因為等到起子執行緒執行退出後,該子執行緒對應的執行緒控制代碼也被CloseHandle()函式關閉了,導致其核心資源被系統回收了,所以我們就不能在主執行緒中呼叫GetExitCodeThread()函式來得倒子執行緒的退出碼,因為這個函式也需要子執行緒的控制代碼作為引數,但此時子執行緒的核心資源已經被系統回收了,即使我們知道它的控制代碼也是沒用的,也就是說,我們需要在呼叫GetExitCodeThread()函式之後,再呼叫CloseHandle()函式,這樣才能得到我們需要的子執行緒函式的退出碼。讀者若是不信的話,可以使用我們上面使用到的例子:使用CreateThread()建立子執行緒的例子,把CloseHandle()放置到GetExitCodeThread()函式之前,看看還能不能得到我們想要的退出碼。本人在此告訴大家,肯定不可以!呵呵!
好了,現在我們終於知道為什麼會這樣了,也知道具體原因了,但該如何解決此問題,是不是意味著我們不能再使用GetExitCodeThread()函式來得到AfxBeginThread函式建立的子執行緒入口函式的退出碼了呢?在這裡,我跟大家說一下,其實是可以的,哈哈!下面,我就提供幾種方法來給大家參考一下!
我們可以使用以下幾個函式來給子執行緒設定退出碼:
TerminateThread();//msdn及網上很多高手都建議不要使用該函式,再此,本人也建議大家慎用此函式,
//想知道原因,哈哈!百度或msdn一下吧!
ExitThread();
AfxEndThread();
_endthreadex();//此函式對應由_beginthreadex() 建立的子執行緒
_endthread();//此函式對應由_beginthread() 建立的子執行緒
以下我就使用AfxBeginThread函式來建立一個子執行緒來為大家演示一下!
一開始還是那句話,先建立一個基於MFC的控制檯專案,或是使用剛才的那個例子,本人比較懶,呵呵,所以我就在原來的那個例子上面做些改動就行啦!
在原始檔開頭定義一個變數和一個子執行緒入口函式:
CWinThread* thread;
UINT ThreadProc(LPVOID lpParam);
接著在main函式的相應位置建立子執行緒,程式碼如下:
子執行緒入口函式如下:
好啦,程式碼已經編寫完成啦,接下來該幹啥呢,哈哈,迫不及待了吧,那就讓我們執行程式試一下咯!
測試效果就如上所以啦,從中可以看出,退出碼為“6”,剛好是我們在子執行緒入口函式裡面設定的退出碼!
好啦,介紹了那麼多,現在大家應該知道怎麼來獲取子執行緒入口函式的退出碼了吧!
其實還有很多地方是需要我們注意的,比方說記憶體遺漏問題等,但在這裡我就不多做介紹啦,相信大家都能夠解決的!
希望此篇文章對各位同仁有所幫助!