_beginthreadex与CreateThread区别与联系
關(guān)于這兩個(gè)函數(shù)的區(qū)別,可以參考《Windows 核心編程(第五版)》的第六章 "線程基礎(chǔ)",這篇文章的思想多數(shù)來源于此,我只是作了一些整理。
線程對于初學(xué)者還說可能覺得很高深,這可以理解。對于某些有經(jīng)驗(yàn)的程序員來說,可能覺得又太簡單,我覺得如果認(rèn)為線程很簡單的人,都是沒有理解線程,線程里面涉及的東西太多,包括內(nèi)存,初始化,線程同步等。我打算以QA的形式來寫這篇文章。
Q:為什么書上說要以_beginthreadex來替代CreateThread?
A:好了,一直用API CreateThread來創(chuàng)建線程的同志們要注意了,你可能會說我一直用這個(gè)API來創(chuàng)建線程,工作剛剛的,一點(diǎn)問題都沒有。如果真是這樣的話,我只能說是你運(yùn)氣太好了。在_beginthreadex的內(nèi)部,它調(diào)用了CreateThread來創(chuàng)建線程,Windows始終用CreateThread來創(chuàng)建線程。在調(diào)用CreateThread之前,beginthreadex它做了很多初始化的工作,所以它比CreateThread創(chuàng)建的線程更加安全。
????Q:為什么要用兩個(gè)相同功能的函數(shù)來對待單線程和多線程序程序呢?
A:這里就有一定的歷史原因了,標(biāo)準(zhǔn)C語言的庫在是1970年左右發(fā)明的,而在那時(shí)候,線程的概念尚未出現(xiàn)在任何一個(gè)操作系統(tǒng)上。但是,線程畢竟是出現(xiàn)了,讓我們來看看下面這個(gè)例子,來說明以前的CRT為什么不支持多線程:
假如代碼是這種情況,當(dāng)執(zhí)行到"system"這個(gè)函數(shù)之后,if之前,操作系統(tǒng)把當(dāng)時(shí)CPU時(shí)鐘周期分配給另一個(gè)線程,而在另一個(gè)線程中正好使用了會設(shè)置errno(這是C語言的一個(gè)全局變量)的CRT函數(shù),于是問題就出現(xiàn)了。
所以早期的CRT函數(shù)是沒有考慮到多線程的,在多線程中還會出問題的CRT函數(shù)還有:strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, asctime, etc. 為了保證C和C+多線程應(yīng)用程序正常運(yùn)行,必須創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu),并使之與使用了C/C+運(yùn)行庫函數(shù)的每個(gè)線程相關(guān)聯(lián),然后在調(diào)用CRT函數(shù)時(shí),那些函數(shù)必須知道去查找主調(diào)線程的數(shù)據(jù)塊,從而避免影響到其他線程。
那么,當(dāng)系統(tǒng)創(chuàng)建線程時(shí),它怎么知道要分配這個(gè)數(shù)據(jù)塊,又應(yīng)該如何分配,它不知道,它也不知道你所調(diào)用的函數(shù)是否是線程安全,所以說,我們在創(chuàng)建新線程時(shí),一定不要調(diào)用操作系統(tǒng)的CreateThread(Windows API)函數(shù),相反,我們始終應(yīng)當(dāng)調(diào)用CRT函數(shù)_beginthreadex,原型如下:
_beginthreadex與CreateThread的對數(shù)列表是一樣的,只是參數(shù)名與類型不同,因?yàn)镃RT函數(shù)不應(yīng)該依賴于Windows的數(shù)據(jù)類型,下面有一個(gè)宏,來將CreateThread函數(shù)替換成_beginthreadex:
注意,_beginthreadex函數(shù)只存在于CRT庫的多線程版本中,如果你的程序鏈接到一個(gè)CRT單線程版本中,那么程序在鏈接時(shí)就會報(bào)錯(cuò),所以在用VS開發(fā)時(shí),要注意這一點(diǎn)。
VS里面設(shè)置如下圖所示:
Q:為什么說_beginthreadex就要比CreateThread更好,你是怎么知道的?
A:由于Microsoft 已經(jīng)為CRT函數(shù)提供了源碼,我們可以看到_beginthreadex到底比CreateThread多做了些什么事情,源碼在Program Files\Microsoft Visual Studio 8\VC\crt\src\Threadex.c中,可以找到_beginthreadex的實(shí)現(xiàn),這里是它的實(shí)現(xiàn):?
_beginthreadex的源碼? [cpp]?view plaincopy我們要明確幾點(diǎn):
1)每個(gè)線程都有自己的專用的_tiddata內(nèi)存塊,它是從C/C++的堆是分配出來的。
2)傳給_beginthreadex的線程處理函數(shù)地址(線程的回調(diào)函數(shù)地址)是存在_tiddata內(nèi)存塊中的。
3)_beginthreadex內(nèi)部的確調(diào)用了CreateThread來創(chuàng)建線程,這(CreateThread)是操作系統(tǒng)創(chuàng)建線程的唯一方式。
4)退出線程時(shí)調(diào)用_endthreadex,它內(nèi)部調(diào)用了API ExitThread,它會釋放創(chuàng)建線程在堆上分配的內(nèi)存_tiddata。
Q:我要怎么終止線程?
A:與_beginthreadex相對應(yīng)的退出線程的函數(shù)是_endthreadex,CreateThread 對應(yīng) ExitThread,一般情況下我們不要調(diào)用這兩個(gè)函數(shù)來終止線程,最好是讓線程走完它的線程處理函數(shù),讓它自生自滅。如果要調(diào)用的話,最好調(diào)用_endthreadex,但一般不推薦。
OK, 目前為止你應(yīng)該對誰更好些的問題有了深入的了解,但是為什么調(diào)用CreateThread的程序仍然可以經(jīng)年累月的正常運(yùn)行呢?當(dāng)線程調(diào)用一個(gè)需要 tiddata結(jié)構(gòu)的CRT函數(shù)時(shí)(大多數(shù)CRT函數(shù)是線程安全的,并不需要該結(jié)構(gòu)),首先CRT函數(shù)試圖獲取線程的數(shù)據(jù)塊的地址(通過調(diào)用 TlsGetValue),然后,如果返回NULL,說明調(diào)用線程沒有相關(guān)聯(lián)的tiddata塊,那么CRT函數(shù)馬上為調(diào)用線程分配并初始化一個(gè) tiddata塊,并將該內(nèi)存塊關(guān)聯(lián)到線程(通過TlsSetValue),這樣,該CRT函數(shù)以及其他CRT函數(shù)都可以使用該線程的tiddata塊了 (此即所謂"前人栽樹后人乘涼"了,_)。
當(dāng)然,如果說你的線程運(yùn)行的時(shí)候一直沒有問題是幾乎不可能的。事實(shí)上,的確有一些問題需要說說。如 果線程使用了CRT的signal函數(shù),整個(gè)進(jìn)程都會被中止,因?yàn)榻Y(jié)構(gòu)化異常處理體尚未準(zhǔn)備好。同樣,如果不調(diào)用_endthreadex來中止線程就會 造成內(nèi)存泄漏,如果使用_beginthreadex,當(dāng)然會容易想到_endthreadex,但如果你習(xí)慣了使用CreateThread,是否還會 想起_endthreadex,我表示極大的懷疑,而且CreateThread/_endthreadex的組合怎么看怎么讓人別扭。
不要忘記 開始的問題,接下來讓我們再來看看效率問題。CRT庫的多線程版本在某些函數(shù)里面放置了同步原語,比如malloc,為了保證堆不會被同時(shí)調(diào)用的 malloc函數(shù)破壞,這不可避免地會對效率造成影響,C/C++的哲學(xué)我們不應(yīng)忘記,"決不為自己沒有用到的付出代價(jià)",自然,我們無權(quán)要求單線程程序 為多線程程序付出它們不該付出的代價(jià),所以,開頭的問題也有了答案。
上面所說的都是靜態(tài)鏈接的CRT庫,而CRT庫的動態(tài)鏈接版本則被編寫得更加 通用,以便能夠被任何運(yùn)行的程序和DLL共享。正是基于這個(gè)原因,這個(gè)版本的庫只存在多線程版本。因?yàn)镃RT庫是以DLL形式提供的,程序和DLL不需要 包含CRT庫的任何代碼,自然尺寸也就更小。同時(shí),如果Microsoft修正了CRT庫DLL中的Bug,程序也就自然受益了。
總結(jié)
首先,如果你調(diào)用_beginthreadex,你會獲得線程的句柄,句柄當(dāng)然需要關(guān)閉,但_endthreadex并沒有這么做。通 常是調(diào)用_beginthreadex的線程(很可能是主線程)來調(diào)用CloseHandle關(guān)閉不再需要的新線程的句柄。其次,如果你使用CRT函數(shù), 你只需要使用_beginthreadex即可。如果不使用,那么你可以只使用CreateThread。同樣,如果只有一個(gè)線程(主線程)使用 CRT,你也可以使用CreateThread;如果新創(chuàng)建的線程不使用CRT,那么你也不需要_beginthreadex和多線程CRT。
超強(qiáng)干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的_beginthreadex与CreateThread区别与联系的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++ POD(Plain Old Da
- 下一篇: C++ POD与结构体声明