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