生活随笔
收集整理的這篇文章主要介紹了
CreateThread和_beginthreadex的区别
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
網址:
http://www.soft-bin.com/html/2010/08/03/createthread_and_beginthreadex.html
在使用VS創建一個工程時,我們可以選擇使用的run-time library,在run-time libary的下拉菜單中,有 Single-Threaded, Multithreaded, MultiThreaded DLL等好幾種選項,不同的選項會為我們鏈接不同的C++ rum-time庫。
之所以run-time庫會有單線程和多線程的區別,是因為標準C運行庫在最初設計時,并沒有考慮到其將被運用于多線程的應用程序,這將會引起一些問題。
例如對于C運行期的全局變量errno,我們在多線程環境下,不能保證在設置errno和獲取errno之間,沒有其他線程對這個全局變量進行了設置,這就會產生一定的問題。而要解決這個問題,我們可以使用線程專屬全局變量 ,這個概念我將在以后講到,但在這個地方,C++ run-time使用的方式是在創建新線程時,為每一個線程分配一個數據塊,來保存這些全局的信息。
在Windows下創建線程調用的是Windows的API函數CreateThread,但CreateThread只是一個與操作系統相關的函數,負責在操作系統內部創建線程內核對象,為線程分配棧空間。為線程的運行準備適宜的條件,并不是操作系統應該考慮的事情。比如說,C++ run-time需要在創建線程時,創建一個線程專屬的errno變量,但可能其他的運行環境又沒有這一項要求,我們不能強制要求Windows操作系統去迎合C++ run-time的需求。
CreateThread并不能正確地初始化C++ run-time對于多線程環境的要求,因此C++ run-time提供了自己的線程創建函數 _beginthreadex,其函數原型如下:
| unsigned?long?_beginthreadex( |
| ????unsigned (*start_address)(void*), |
這個函數的參數和CreateThread的參數基本一致,不同的是參數類型不同。這是因為C++ run-time并不從屬于操作系統,不應當對操作系統的數據類型有任何依賴。因此我們可以看到 _beginthreadex 函數的參數完全是基本的C++數據類型。
_beginthreadex 函數將會創建線程專屬的數據塊,并對其進行初始化,而后調用操作系統的線程創建函數以創建一個線程,其偽碼如下:
?
| unsigned?long?_beginthreadex( |
| ????unsigned (*start_address)(void*), |
| ????_ptiddata ptd;??// 指向線程專屬數據塊的指針,這個結構不作詳細介紹,只需知道其中包含線程專屬全局變量 |
| ????unsigned?long?thdl;???// 線程句柄 |
| ????ptd = _calloc_crt(1,?sizeof(struct?tiddata)); |
| ????initptd(ptd);???// 初始化 |
| ????ptd->_initaddr = (void*)pfnStartAddr;????// 保存線程函數地址 |
| ????ptd->_initarg = pvParam;???// 保存線程函數的參數 |
| ?????thdl = (unsigned?long) CreateThread(pas, cbStack, _threadstartex, (PVOID)ptd, fdwCreate, pdwThreadID); |
| ???????????_free_crt(ptd); |
我們可以看到,_beginthreadex首先創建了一個線程專屬數據塊,這個數據塊中自然就保存了errno之類的線程專屬數據,至于如何獲取和設置這些數據,稍后我會提到。 創建線程仍然是調用的Windows API函數CreateThread,因為線程的創建與操作系統息息相關,我們唯有通過其API函數CreateThread才可以創建之。
_beginthreadex 將線程的入口函數變更為了 _threadstartex,我們可以看其偽碼:
| static?unsigned?long?WINAPI _threadstartex(void* ptd) |
| ????// 保存ptd,我們在其他地方會再獲取ptd |
| ????TlsSetValue(__tlsidnex, ptd);? |
| ????((_ptiddata) ptd)->_tid = GetCurrentThreadId(); |
| ????????unsigned ret = ptd->_initaddr(ptd->_initarg); |
| ????????_endthreadex(ret); |
| ????????_exit(GetExceptionCode()); |
我們可以看到 _threadstartex 仍然是調用了 _beginthreadex 函數中最初傳入的線程函數,不同的是,在調用線程函數之前,我們使用了異常捕獲,防止線程函數崩潰導致程序崩潰。在線程函數執行完畢后,_threadstartex會緊接著調用 _endthreadex函數,這個函數的作用有兩點:
1. 釋放線程專屬數據塊,避免內存泄漏
2. 調用ExitThread退出線程,將退出碼寫入到線程內核對象中
至此我們可以知道,CreateThread和_beginthreadex兩個函數的區別在于:
CreateThread是操作系統提供的創建線程的API,_beginthreadex是C++ run-time提供的用于對線程進行初始化,創建線程轉悠數據塊的函數。其內部創建線程的操作,仍然通過CreateThread來實現。
我們在編寫C++程序創建線程時,應當使用_beginthreadex,而絕不要使用CreateThread,否則會引起錯誤。
在我們使用_beginthreadex函數時,必須選擇run-time鏈接選項為多線程庫,否則會出現找不到_beginthreadex的錯誤。
我們之前提到過errno這個全局變量,在run-time為多線程庫的時候,它實際上是一個宏:
| #if defined(_MT) || defined(_DLL) |
| ????extern?int?* __cdecl _errno(void); |
| ????#define errno (*_errno()) |
我們在選擇多線程庫后,使用errno時,實際上是調用了多線程庫中的 _errno()函數,這個函數會將線程專屬數據塊中的errno值獲取出來。
可以注意到,_errno() 實際上是取得了線程專屬數據塊中errno的地址,因此我們也可以對errno進行設置,其行為就如同直接使用一個int類型變量一樣。
總結
以上是生活随笔為你收集整理的CreateThread和_beginthreadex的区别的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。