日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

_beginthreadex与CreateThread区别与联系

發布時間:2024/4/11 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 _beginthreadex与CreateThread区别与联系 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關于這兩個函數的區別,可以參考《Windows 核心編程(第五版)》的第六章 "線程基礎",這篇文章的思想多數來源于此,我只是作了一些整理。

線程對于初學者還說可能覺得很高深,這可以理解。對于某些有經驗的程序員來說,可能覺得又太簡單,我覺得如果認為線程很簡單的人,都是沒有理解線程,線程里面涉及的東西太多,包括內存,初始化,線程同步等。我打算以QA的形式來寫這篇文章。


Q:為什么書上說要以_beginthreadex來替代CreateThread?

A:好了,一直用API CreateThread來創建線程的同志們要注意了,你可能會說我一直用這個API來創建線程,工作剛剛的,一點問題都沒有。如果真是這樣的話,我只能說是你運氣太好了。在_beginthreadex的內部,它調用了CreateThread來創建線程,Windows始終用CreateThread來創建線程。在調用CreateThread之前,beginthreadex它做了很多初始化的工作,所以它比CreateThread創建的線程更加安全。


????Q:為什么要用兩個相同功能的函數來對待單線程和多線程序程序呢?

A:這里就有一定的歷史原因了,標準C語言的庫在是1970年左右發明的,而在那時候,線程的概念尚未出現在任何一個操作系統上。但是,線程畢竟是出現了,讓我們來看看下面這個例子,來說明以前的CRT為什么不支持多線程:

[cpp]?view plaincopy
  • BOOL?fFailure?=?(system("NOTEPAD.EXE?README.TXT")?==?-1);???
  • ????if?(fFailure)??
  • ????{???
  • ???????????switch?(errno)??
  • ???????????{??
  • ???????????case?E2BIG:??
  • ??????????????????//?Argument?list?or?environment?too?big???
  • ??????????????????break;???
  • ???????????case?ENOENT:???
  • ??????????????????//?Command?interpreter?cannot?be?found???
  • ??????????????????break;???
  • ???????????case?ENOEXEC:???
  • ??????????????????//?Command?interpreter?has?bad?format???
  • ??????????????????break;???
  • ???????????case?ENOMEM:???
  • ??????????????????//?Insufficient?memory?to?run?command???
  • ??????????????????break;??
  • ???????????}??
  • ????}??

  • 假如代碼是這種情況,當執行到"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,原型如下:

    [cpp]?view plaincopy
  • unsigned?long?_beginthreadex??
  • ?????????????????????(??
  • ?????????????????????void?*security,??
  • ?????????????????????unsigned?stack_size,???
  • ?????????????????????unsigned?(*start_address)(void?*),??
  • ?????????????????????void?*arglist,???
  • ?????????????????????unsigned?initflag,???
  • ?????????????????????unsigned?*thrdaddr??
  • ?????????????????????);??

  • _beginthreadex與CreateThread的對數列表是一樣的,只是參數名與類型不同,因為CRT函數不應該依賴于Windows的數據類型,下面有一個宏,來將CreateThread函數替換成_beginthreadex:

    [cpp]?view plaincopy
  • typedef?unsigned?(__stdcall?*?PTHREAD_START)?(void?*);???
  • ????#define?chBEGINTHREADEX(psa,?cbStack,?pfnStartAddr,?\???
  • ??????????????????????????pvParam,?fdwCreate,?pdwThreadID)?\???
  • ??????????????????????????((HANDLE)?_beginthreadex(?\???
  • ??????????????????????????(void?*)?(psa),?\??
  • ???????????????????????????(unsigned)?(cbStack),?\???
  • ??????????????????????????(PTHREAD_START)?(pfnStartAddr),\??
  • ???????????????????????????(void?*)?(pvParam),\??
  • ???????????????????????????(unsigned)?(fdwCreate),?\???
  • ??????????????????????????(unsigned?*)?(pdwThreadID)))??

  • 注意,_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
  • _MCRTIMP?uintptr_t?__cdecl?_beginthreadex?(??
  • ????????void?*security,??
  • ????????unsigned?stacksize,??
  • ????????unsigned?(__CLR_OR_STD_CALL?*?initialcode)?(void?*),??
  • ????????void?*?argument,??
  • ????????unsigned?createflag,??
  • ????????unsigned?*thrdaddr??
  • ????????)??
  • {??
  • ????????_ptiddata?ptd;????????????????????
  • ????????uintptr_t?thdl;???????????????????
  • ????????unsigned?long?err?=?0L;???????
  • ????????unsigned?dummyid;?????????????????
  • ??
  • ??????????
  • ????????_VALIDATE_RETURN(initialcode?!=?NULL,?EINVAL,?0);??
  • ??
  • ??????????
  • ????????__set_flsgetvalue();??
  • ??
  • ??????????
  • ????????if?(?(ptd?=?(_ptiddata)_calloc_crt(1,?sizeof(struct?_tiddata)))?==?NULL?)??
  • ????????????????goto?error_return;??
  • ??
  • ??????????
  • ??
  • ????????_initptd(ptd,?_getptd()->ptlocinfo);??
  • ??
  • ????????ptd->_initaddr?=?(void?*)?initialcode;??
  • ????????ptd->_initarg?=?argument;??
  • ????????ptd->_thandle?=?(uintptr_t)(-1);??
  • ??
  • #if?defined?(_M_CEE)?||?defined?(MRTDLL)??
  • ????????if(!_getdomain(&(ptd->__initDomain)))??
  • ????????{??
  • ????????????goto?error_return;??
  • ????????}??
  • #endif????
  • ??
  • ??????????
  • ????????if?(?thrdaddr?==?NULL?)??
  • ????????????????thrdaddr?=?&dummyid;??
  • ??
  • ??????????
  • ????????if?(?(thdl?=?(uintptr_t)??
  • ??????????????CreateThread(?(LPSECURITY_ATTRIBUTES)security,??
  • ????????????????????????????stacksize,??
  • ????????????????????????????_threadstartex,??
  • ????????????????????????????(LPVOID)ptd,??
  • ????????????????????????????createflag,??
  • ????????????????????????????(LPDWORD)thrdaddr))??
  • ?????????????==?(uintptr_t)0?)??
  • ????????{??
  • ????????????????err?=?GetLastError();??
  • ????????????????goto?error_return;??
  • ????????}??
  • ??
  • ??????????
  • ????????return(thdl);??
  • ??
  • ??????????
  • error_return:??
  • ??????????
  • ????????_free_crt(ptd);??
  • ??
  • ??????????
  • ????????if?(?err?!=?0L?)??
  • ????????????????_dosmaperr(err);??
  • ??
  • ????????return(?(uintptr_t)0?);??
  • }??
  • 我們要明確幾點:

    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区别与联系的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。