国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C > 正文

CreateThread()與beginthread()的區別詳細解析

2020-01-26 15:51:18
字體:
來源:轉載
供稿:網友

我們知道在Windows下創建一個線程的方法有兩種,一種就是調用Windows API CreateThread()來創建線程;另外一種就是調用MSVC CRT的函數_beginthread()或_beginthreadex()來創建線程。相應的退出線程也有兩個函數Windows API的ExitThread()和CRT的_endthread()。這兩套函數都是用來創建和退出線程的,它們有什么區別呢?

很多開發者不清楚這兩者之間的關系,他們隨意選一個函數來用,發現也沒有什么大問題,于是就忙于解決更為緊迫的任務去了,而沒有對它們進行深究。等到有一天忽然發現一個程序運行時間很長的時候會有細微的內存泄露,開發者絕對不會想到是因為這兩套函數用混的結果。

根據Windows API和MSVC CRT的關系,可以看出來_beginthread()是對CreateThread()的包裝,它最終還是調用CreateThread()來創建線程。那么在_beginthread()調用CreateThread()之前做了什么呢?我們可以看一下_beginthread()的源代碼,它位于CRT源代碼中的thread.c。我們可以發現它在調用CreateThread()之前申請了一個叫_tiddata的結構,然后將這個結構用_initptd()函數初始化之后傳遞給_beginthread()自己的線程入口函數_threadstart。_threadstart首先把由_beginthread()傳過來的_tiddata結構指針保存到線程的顯式TLS數組,然后它調用用戶的線程入口真正開始線程。在用戶線程結束之后,_threadstart()函數調用_endthread()結束線程。并且_threadstart還用__try/__except將用戶線程入口函數包起來,用于捕獲所有未處理的信號,并且將這些信號交給CRT處理。

所以除了信號之外,很明顯CRT包裝Windows API線程接口的最主要目的就是那個_tiddata。這個線程私有的結構里面保存的是什么呢?我們可以從mtdll.h中找到它的定義,它里面保存的是諸如線程ID、線程句柄、erron、strtok()的前一次調用位置、rand()函數的種子、異常處理等與CRT有關的而且是線程私有的信息??梢奙SVC CRT并沒有使用我們前面所說的__declspec(thread)這種方式來定義線程私有變量,從而防止庫函數在多線程下失效,而是采用在堆上申請一個_tiddata結構,把線程私有變量放在結構內部,由顯式TLS保存_tiddata的指針。

了解了這些信息以后,我們應該會想到一個問題,那就是如果我們用CreateThread()創建一個線程然后調用CRT的strtok()函數,按理說應該會出錯,因為strtok()所需要的_tiddata并不存在,可是我們好像從來沒碰到過這樣的問題。查看strtok()函數就會發現,當一開始調用_getptd()去得到線程的_tiddata結構時,這個函數如果發現線程沒有申請_tiddata結構,它就會申請這個結構并且負責初始化。于是無論我們調用哪個函數創建線程,都可以安全調用所有需要_tiddata的函數,因為一旦這個結構不存在,它就會被創建出來。

那么_tiddata在什么時候會被釋放呢?ExitThread()肯定不會,因為它根本不知道有_tiddata這樣一個結構存在,那么很明顯是_endthread()釋放的,這也正是CRT的做法。不過我們很多時候會發現,即使使用CreateThread()和ExitThread() (不調用ExitThread()直接退出線程函數的效果相同),也不會發現任何內存泄露,這又是為什么呢?經過仔細檢查之后,我們發現原來密碼在CRT DLL的入口函數DllMain中。我們知道,當一個進程/線程開始或退出的時候,每個DLL的DllMain都會被調用一次,于是動態鏈接版的CRT就有機會在DllMain中釋放線程的_tiddata??墒荄llMain只有當CRT是動態鏈接版的時候才起作用,靜態鏈接CRT是沒有DllMain的!這就是造成使用CreateThread()會導致內存泄露的一種情況,在這種情況下,_tiddata在線程結束時無法釋放,造成了泄露。

我們可以用下面這個小程序來測試:

復制代碼 代碼如下:

#include <Windows.h>
#include <process.h>
void thread(void *a)
{
    char* r = strtok( "aaa", "b" );
    ExitThread(0); // 這個函數是否調用都無所謂
}
int main(int argc, char* argv[])
{
    while(1) {
        CreateThread(  0, 0, (LPTHREAD_START_ROUTINE)thread, 0, 0, 0 );
        Sleep( 5 );
    }
return 0;
}

如果用動態鏈接的CRT (/MD,/MDd)就不會有問題,但是,如果使用靜態鏈接CRT (/MT,/MTd),運行程序后在進程管理器中觀察它就會發現內存用量不停地上升,但是如果我們把thread()函數中的ExitThread()改成_endthread()就不會有問題,因為_endthread()會將_tiddata()釋放。

這個問題可以總結為:當使用CRT時(基本上所有的程序都使用CRT),請盡量使用_beginthread()/_beginthreadex()/_endthread()/_endthreadex()這組函數來創建線程。在MFC中,還有一組類似的函數是AfxBeginThread()和AfxEndThread(),根據上面的原理類推,它是MFC層面的線程包裝函數,它們會維護線程與MFC相關的結構,當我們使用MFC類庫時,盡量使用它提供的線程包裝函數以保證程序運行正確。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 永顺县| 西吉县| 原阳县| 大洼县| 济宁市| 江安县| 泰来县| 佳木斯市| 应用必备| 策勒县| 天门市| 海阳市| 怀来县| 翼城县| 临安市| 蒙城县| 通许县| 荣昌县| 江达县| 江华| 秀山| 蕉岭县| 桐庐县| 灯塔市| 阿合奇县| 绥德县| 亳州市| 綦江县| 东乌珠穆沁旗| 龙陵县| 濮阳县| 南丹县| 新兴县| 东平县| 瑞丽市| 醴陵市| 康平县| 饶河县| 昭通市| 绍兴县| 渝北区|