秒殺多線程第二篇 多線程第一次親密接觸 CreateThread與_beginthreadex本質區別

?? 本文將帶領你與多線程作第一次親密接觸,并深入分析CreateThread_beginthreadex的本質區別,相信閱讀本文后你能輕松的使用多線程并能流暢準確的回答CreateThread_beginthreadex到底有什么區別,在實際的編程中到底應該使用CreateThread還是_beginthreadex

?

?? 使用多線程其實是非常容易的,下面這個程序的主線程會創建了一個子線程并等待其運行完畢,子線程就輸出它的線程ID號然后輸出一句經典名言——Hello World。整個程序的代碼非常簡短,只有區區幾行。

[cpp] view plaincopy
  1. //最簡單的創建多線程實例??
  2. #include?<stdio.h>??
  3. #include?<windows.h>??
  4. //子線程函數??
  5. DWORD?WINAPI?ThreadFun(LPVOID?pM)??
  6. {??
  7. ????printf("子線程的線程ID號為:%d\n子線程輸出Hello?World\n",?GetCurrentThreadId());??
  8. ????return?0;??
  9. }??
  10. //主函數,所謂主函數其實就是主線程執行的函數。??
  11. int?main()??
  12. {??
  13. ????printf("?????最簡單的創建多線程實例\n");??
  14. ????printf("?--?by?MoreWindows(?http://blog.csdn.net/MoreWindows?)?--\n\n");??
  15. ??
  16. ????HANDLE?handle?=?CreateThread(NULL,?0,?ThreadFun,?NULL,?0,?NULL);??
  17. ????WaitForSingleObject(handle,?INFINITE);??
  18. ????return?0;??
  19. }??

運行結果如下所示:

下面來細講下代碼中的一些函數

第一個 CreateThread

函數功能:創建線程

函數原型:

HANDLEWINAPICreateThread(

?LPSECURITY_ATTRIBUTESlpThreadAttributes,

?SIZE_TdwStackSize,

??LPTHREAD_START_ROUTINElpStartAddress,

?LPVOIDlpParameter,

?DWORDdwCreationFlags,

?LPDWORDlpThreadId

);

函數說明:

第一個參數表示線程內核對象的安全屬性,一般傳入NULL表示使用默認設置。

第二個參數表示線程棧空間大小。傳入0表示使用默認大小(1MB)。

第三個參數表示新線程所執行的線程函數地址,多個線程可以使用同一個函數地址。

第四個參數是傳給線程函數的參數。

第五個參數指定額外的標志來控制線程的創建,為0表示線程創建之后立即就可以進行調度,如果為CREATE_SUSPENDED則表示線程創建后暫停運行,這樣它就無法調度,直到調用ResumeThread()

第六個參數將返回線程的ID號,傳入NULL表示不需要返回該線程ID號。

函數返回值:

成功返回新線程的句柄,失敗返回NULL。?

?

第二個 WaitForSingleObject

函數功能:等待函數使線程進入等待狀態,直到指定的內核對象被觸發。

函數原形:

DWORDWINAPIWaitForSingleObject(

?HANDLEhHandle,

?DWORDdwMilliseconds

);

函數說明:

第一個參數為要等待的內核對象。

第二個參數為最長等待的時間,以毫秒為單位,如傳入5000就表示5秒,傳入0就立即返回,傳入INFINITE表示無限等待。

因為線程的句柄在線程運行時是未觸發的,線程結束運行,句柄處于觸發狀態。所以可以用WaitForSingleObject()來等待一個線程結束運行。

函數返回值:

在指定的時間內對象被觸發,函數返回WAIT_OBJECT_0。超過最長等待時間對象仍未被觸發返回WAIT_TIMEOUT。傳入參數有錯誤將返回WAIT_FAILED

?

CreateThread()函數是Windows提供的API接口,在C/C++語言另有一個創建線程的函數_beginthreadex(),在很多書上(包括《Windows核心編程》)提到過盡量使用_beginthreadex()來代替使用CreateThread(),這是為什么了?下面就來探索與發現它們的區別吧。

?

?????? 首先要從標準C運行庫與多線程的矛盾說起,標準C運行庫在1970年被實現了,由于當時沒任何一個操作系統提供對多線程的支持。因此編寫標準C運行庫的程序員根本沒考慮多線程程序使用標準C運行庫的情況。比如標準C運行庫的全局變量errno。很多運行庫中的函數在出錯時會將錯誤代號賦值給這個全局變量,這樣可以方便調試。但如果有這樣的一個代碼片段:

[cpp] view plaincopy
  1. if?(system("notepad.exe?readme.txt")?==?-1)??
  2. {??
  3. ????switch(errno)??
  4. ????{??
  5. ????????...//錯誤處理代碼??
  6. ????}??
  7. }??

假設某個線程A在執行上面的代碼,該線程在調用system()之后且尚未調用switch()語句時另外一個線程B啟動了,這個線程B也調用了標準C運行庫的函數,不幸的是這個函數執行出錯了并將錯誤代號寫入全局變量errno中。這樣線程A一旦開始執行switch()語句時,它將訪問一個被B線程改動了的errno。這種情況必須要加以避免!因為不單單是這一個變量會出問題,其它像strerror()strtok()tmpnam()gmtime()asctime()等函數也會遇到這種由多個線程訪問修改導致的數據覆蓋問題。

?

為了解決這個問題,Windows操作系統提供了這樣的一種解決方案——每個線程都將擁有自己專用的一塊內存區域來供標準C運行庫中所有有需要的函數使用。而且這塊內存區域的創建就是由C/C++運行庫函數_beginthreadex()來負責的。下面列出_beginthreadex()函數的源代碼(我在這份代碼中增加了一些注釋)以便讀者更好的理解_beginthreadex()函數與CreateThread()函數的區別。

[cpp] view plaincopy
  1. //_beginthreadex源碼整理By?MoreWindows(?http://blog.csdn.net/MoreWindows?)??
  2. _MCRTIMP?uintptr_t?__cdecl?_beginthreadex(??
  3. ????void?*security,??
  4. ????unsigned?stacksize,??
  5. ????unsigned?(__CLR_OR_STD_CALL?*?initialcode)?(void?*),??
  6. ????void?*?argument,??
  7. ????unsigned?createflag,??
  8. ????unsigned?*thrdaddr??
  9. )??
  10. {??
  11. ????_ptiddata?ptd;??????????//pointer?to?per-thread?data?見注1??
  12. ????uintptr_t?thdl;?????????//thread?handle?線程句柄??
  13. ????unsigned?long?err?=?0L;?//Return?from?GetLastError()??
  14. ????unsigned?dummyid;????//dummy?returned?thread?ID?線程ID號??
  15. ??????
  16. ????//?validation?section?檢查initialcode是否為NULL??
  17. ????_VALIDATE_RETURN(initialcode?!=?NULL,?EINVAL,?0);??
  18. ??
  19. ????//Initialize?FlsGetValue?function?pointer??
  20. ????__set_flsgetvalue();??
  21. ??????
  22. ????//Allocate?and?initialize?a?per-thread?data?structure?for?the?to-be-created?thread.??
  23. ????//相當于new一個_tiddata結構,并賦給_ptiddata指針。??
  24. ????if?(?(ptd?=?(_ptiddata)_calloc_crt(1,?sizeof(struct?_tiddata)))?==?NULL?)??
  25. ????????goto?error_return;??
  26. ??
  27. ????//?Initialize?the?per-thread?data??
  28. ????//初始化線程的_tiddata塊即CRT數據區域?見注2??
  29. ????_initptd(ptd,?_getptd()->ptlocinfo);??
  30. ??????
  31. ????//設置_tiddata結構中的其它數據,這樣這塊_tiddata塊就與線程聯系在一起了。??
  32. ????ptd->_initaddr?=?(void?*)?initialcode;?//線程函數地址??
  33. ????ptd->_initarg?=?argument;??????????????//傳入的線程參數??
  34. ????ptd->_thandle?=?(uintptr_t)(-1);??
  35. ??????
  36. #if?defined?(_M_CEE)?||?defined?(MRTDLL)??
  37. ????if(!_getdomain(&(ptd->__initDomain)))?//見注3??
  38. ????{??
  39. ????????goto?error_return;??
  40. ????}??
  41. #endif??//?defined?(_M_CEE)?||?defined?(MRTDLL)??
  42. ??????
  43. ????//?Make?sure?non-NULL?thrdaddr?is?passed?to?CreateThread??
  44. ????if?(?thrdaddr?==?NULL?)//判斷是否需要返回線程ID號??
  45. ????????thrdaddr?=?&dummyid;??
  46. ??
  47. ????//?Create?the?new?thread?using?the?parameters?supplied?by?the?caller.??
  48. ????//_beginthreadex()最終還是會調用CreateThread()來向系統申請創建線程??
  49. ????if?(?(thdl?=?(uintptr_t)CreateThread(??
  50. ????????????????????(LPSECURITY_ATTRIBUTES)security,??
  51. ????????????????????stacksize,??
  52. ????????????????????_threadstartex,??
  53. ????????????????????(LPVOID)ptd,??
  54. ????????????????????createflag,??
  55. ????????????????????(LPDWORD)thrdaddr))??
  56. ????????==?(uintptr_t)0?)??
  57. ????{??
  58. ????????err?=?GetLastError();??
  59. ????????goto?error_return;??
  60. ????}??
  61. ??
  62. ????//Good?return??
  63. ????return(thdl);?//線程創建成功,返回新線程的句柄.??
  64. ??????
  65. ????//Error?return??
  66. error_return:??
  67. ????//Either?ptd?is?NULL,?or?it?points?to?the?no-longer-necessary?block??
  68. ????//calloc-ed?for?the?_tiddata?struct?which?should?now?be?freed?up.??
  69. ????//回收由_calloc_crt()申請的_tiddata塊??
  70. ????_free_crt(ptd);??
  71. ????//?Map?the?error,?if?necessary.??
  72. ????//?Note:?this?routine?returns?0?for?failure,?just?like?the?Win32??
  73. ????//?API?CreateThread,?but?_beginthread()?returns?-1?for?failure.??
  74. ????//校正錯誤代號(可以調用GetLastError()得到錯誤代號)??
  75. ????if?(?err?!=?0L?)??
  76. ????????_dosmaperr(err);??
  77. ????return(?(uintptr_t)0?);?//返回值為NULL的效句柄??
  78. }??

講解下部分代碼:

1_ptiddataptd;中的_ptiddata是個結構體指針。在mtdll.h文件被定義:

????? typedefstruct_tiddata *_ptiddata

微軟對它的注釋為Structure for each thread's data這是一個非常大的結構體,有很多成員。本文由于篇幅所限就不列出來了。

?

2_initptd(ptd,_getptd()->ptlocinfo);微軟對這一句代碼中的getptd()的說明為:

?????/* return address of per-thread CRT data */

?????_ptiddata __cdecl_getptd(void);

_initptd()說明如下:

?????/* initialize a per-thread CRT data block */

?????void__cdecl_initptd(_Inout_ _ptiddata_Ptd,_In_opt_ pthreadlocinfo_Locale);

注釋中的CRTC Runtime Library)即標準C運行庫。

?

3if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函數代碼可以在thread.c文件中找到,其主要功能是初始化COM環境。

?

由上面的源代碼可知,_beginthreadex()函數在創建新線程時會分配并初始化一個_tiddata塊。這個_tiddata塊自然是用來存放一些需要線程獨享的數據。事實上新線程運行時會首先將_tiddata塊與自己進一步關聯起來。然后新線程調用標準C運行庫函數如strtok()時就會先取得_tiddata塊的地址再將需要保護的數據存入_tiddata塊中。這樣每個線程就只會訪問和修改自己的數據而不會去篡改其它線程的數據了。因此,如果在代碼中有使用標準C運行庫中的函數時,盡量使用_beginthreadex()來代替CreateThread()相信閱讀到這里時,你會對這句簡短的話有個非常深刻的印象,如果有面試官問起,你也可以流暢準確的回答了^_^

?

接下來,類似于上面的程序用CreateThread()創建輸出“Hello World”的子線程,下面使用_beginthreadex()來創建多個子線程:

[cpp] view plaincopy
  1. //創建多子個線程實例??
  2. #include?<stdio.h>??
  3. #include?<process.h>??
  4. #include?<windows.h>??
  5. //子線程函數??
  6. unsigned?int?__stdcall?ThreadFun(PVOID?pM)??
  7. {??
  8. ????printf("線程ID號為%4d的子線程說:Hello?World\n",?GetCurrentThreadId());??
  9. ????return?0;??
  10. }??
  11. //主函數,所謂主函數其實就是主線程執行的函數。??
  12. int?main()??
  13. {??
  14. ????printf("?????創建多個子線程實例?\n");??
  15. ????printf("?--?by?MoreWindows(?http://blog.csdn.net/MoreWindows?)?--\n\n");??
  16. ??????
  17. ????const?int?THREAD_NUM?=?5;??
  18. ????HANDLE?handle[THREAD_NUM];??
  19. ????for?(int?i?=?0;?i?<?THREAD_NUM;?i++)??
  20. ????????handle[i]?=?(HANDLE)_beginthreadex(NULL,?0,?ThreadFun,?NULL,?0,?NULL);??
  21. ????WaitForMultipleObjects(THREAD_NUM,?handle,?TRUE,?INFINITE);??
  22. ????return?0;??
  23. }??

運行結果如下:

圖中每個子線程說的都是同一句話,不太好看。能不能來一個線程報數功能,即第一個子線程輸出1,第二個子線程輸出2,第三個子線程輸出3,……。要實現這個功能似乎非常簡單——每個子線程對一個全局變量進行遞增并輸出就可以了。代碼如下:

[cpp] view plaincopy
  1. //子線程報數??
  2. #include?<stdio.h>??
  3. #include?<process.h>??
  4. #include?<windows.h>??
  5. int?g_nCount;??
  6. //子線程函數??
  7. unsigned?int?__stdcall?ThreadFun(PVOID?pM)??
  8. {??
  9. ????g_nCount++;??
  10. ????printf("線程ID號為%4d的子線程報數%d\n",?GetCurrentThreadId(),?g_nCount);??
  11. ????return?0;??
  12. }??
  13. //主函數,所謂主函數其實就是主線程執行的函數。??
  14. int?main()??
  15. {??
  16. ????printf("?????子線程報數?\n");??
  17. ????printf("?--?by?MoreWindows(?http://blog.csdn.net/MoreWindows?)?--\n\n");??
  18. ??????
  19. ????const?int?THREAD_NUM?=?10;??
  20. ????HANDLE?handle[THREAD_NUM];??
  21. ??
  22. ????g_nCount?=?0;??
  23. ????for?(int?i?=?0;?i?<?THREAD_NUM;?i++)??
  24. ????????handle[i]?=?(HANDLE)_beginthreadex(NULL,?0,?ThreadFun,?NULL,?0,?NULL);??
  25. ????WaitForMultipleObjects(THREAD_NUM,?handle,?TRUE,?INFINITE);??
  26. ????return?0;??
  27. }??

對一次運行結果截圖如下:

顯示結果從1數到10,看起來好象沒有問題。

?????? 答案是不對的,雖然這種做法在邏輯上是正確的,但在多線程環境下這樣做是會產生嚴重的問題,下一篇《秒殺多線程第三篇 原子操作 Interlocked系列函數》將為你演示錯誤的結果(可能非常出人意料)并解釋產生這個結果的詳細原因。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254864.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254864.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254864.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

halcon get_image_pointer1獲取圖像指針

目錄get_image_pointer1&#xff08;算子&#xff09;描述參數get_image_pointer1&#xff08;算子&#xff09; get_image_pointer1 - 訪問單通道圖像的指針。 get_image_pointer1(Image : : : Pointer, Type, Width, Height) 描述 運算符get_image_pointer1返回指向圖像I…

如何傳輸文件到linux服務器?

我們知道&#xff0c;云主機文件傳輸是一件相對復雜的事情&#xff0c;經常需要搭建FTP服務器或者是借助其他工具來完成。下面為大家介紹一種簡單易操作的傳輸文件到Linux服務器的方法。 Linux文件傳輸同Windows文件傳輸一樣&#xff0c;我們為每一臺Linux主機配置了一個1G的網…

C++學習筆記(五)--指針、NULL、引用

1. C中已經定義了NULL為0:#define NULL 0 指針p可以指向空值NULL即 p NULL;表示該指針變量不指向任何變量。   注意&#xff1a;指針未初始化與指針為NULL不同&#xff0c;   p NULL;是有值的&#xff0c;為0&#xff1b;   而在定義時&#xff1a;int *p;這時候碎雖然…

Sort函數的用法

快速排序sort的用法&#xff1a;&#xff08;適用于int float double char 。。。&#xff09; 記得加頭文件&#xff01; 記得加頭文件&#xff01; 記得加頭文件&#xff01; 頭文件&#xff1a; #include <algorithm> using namespace std ; // 兩行都要寫 數組排…

crf與bitrate對照表

crf與bitrate對照表 (2011-06-21 17:45:59)一些關于crf的備忘&#xff1a; 1、相較于bitrate方式&#xff0c;cpu占用與內存占用均會下降&#xff1b; 2、銳化濾鏡會讓crf的碼率上升&#xff1b; 3、vbv對crf依然有效&#xff1b; 4、crf18就接近無損&#xff0c;字幕組慣用20-…

秒殺多線程第三篇 原子操作 Interlocked系列函數

上一篇《多線程第一次親密接觸 CreateThread與_beginthreadex本質區別》中講到一個多線程報數功能。為了描述方便和代碼簡潔起見&#xff0c;我們可以只輸出最后的報數結果來觀察程序是否運行出錯。這也非常類似于統計一個網站每天有多少用戶登錄&#xff0c;每個用戶登錄用一個…

Vue 教程第九篇—— 動畫和過度效果

過渡效果 SPA 中組件的切換有一種生硬的隱藏顯示感覺&#xff0c;為了更好的用戶體驗&#xff0c;讓組件切換時淡出淡入&#xff0c;Vue 提供了專門的組件 transition。 過濾效果應用場景 條件渲染 (使用 v-if)條件展示 (使用 v-show)動態組件組件根節點過渡狀態 enter&#xf…

halcon create_ocr_class_svm 使用SVM分類器創建OCR分類器

目錄create_ocr_class_svm&#xff08;算子&#xff09;描述參數create_ocr_class_svm&#xff08;算子&#xff09; create_ocr_class_svm - 使用支持隨機向量機制創建OCR分類器。 create_ocr_class_svm&#xff08;:: WidthCharacter&#xff0c;HeightCharacter&#xff0…

碼率跟視頻質量有關系

碼率跟視頻質量有關系.首先要清楚, 相同的視頻編碼方式下, 碼率越高肯定畫面越清晰. 但是高到一定值, 再往上的畫面改善程度就不明顯了, 只會增大文件體積. 所以碼率選的合適, 才可以保證清晰度又保持文件不會太大. 個人經驗如果是h.264編碼(當前最好的視頻壓縮編碼方案), …

SQL 字符串分割表函數

1 --字符串分割表函數2 declare str varchar(1000)3 declare split varchar(10) 4 5 declare i int;6 declare count int;7 8 declare ChildStr varchar(1000);9 declare splitStr varchar(1000); 10 declare Index int; 11 12 declare table as table (rowId int,splitStr va…

語句:分支語句、switch case ——7月22日

語句的類型包括&#xff1a;聲明語句、表達式語句、選擇語句、循環語句、跳轉語句、異常語句 1&#xff0e;聲明語句引&#xff1a;入新的變量或常量。 變量聲明可以選擇為變量賦值。 在常量聲明中必須賦值。 例如&#xff1a; int i 0;//聲明變量i 并賦值&#xff0c;也可以不…

halcon write_ocr_trainf 將訓練字符存儲到文件中

目錄write_ocr_trainf&#xff08;運算符&#xff09;描述參數write_ocr_trainf&#xff08;運算符&#xff09; write_ocr_trainf - 將訓練字符存儲到文件中。 write_ocr_trainf&#xff08;Character&#xff0c;Image :: Class&#xff0c;TrainingFile ? 描述 運算符w…

碼率計算文章

http://bbs.dvbcn.com/showtopic-41431-1.html

PostgreSQL Oracle 兼容性之 - INDEX SKIP SCAN (遞歸查詢變態優化) 非驅動列索引掃描優化...

標簽 PostgreSQL , Oracle , index skip scan , 非驅動列條件 , 遞歸查詢 , 子樹 背景 對于輸入條件在復合索引中為非驅動列的&#xff0c;如何高效的利用索引掃描&#xff1f; 在Oracle中可以使用index skip scan來實現這類CASE的高效掃描&#xff1a; INDEX跳躍掃描一般用在W…

如何確定鏡頭CCD靶面尺寸?

在組建機器視覺系統時&#xff0c;需要選用適合實際應用的產品。今天&#xff0c;中國機器視覺商城的培訓課堂為您帶來的是關于工業鏡頭CCD靶面尺寸的確定方法。 在選擇鏡頭時&#xff0c;我們通常要注意一個原則&#xff1a;即小尺寸靶面的CCD可使用對應規格更大的鏡頭&#x…

lua去掉字符串中的UTF-8的BOM三個字節

廢話不多說&#xff0c;還是先說點吧&#xff0c;項目中lua讀取的text文件如果有BOM&#xff0c;客戶端解析就會報錯&#xff0c;所以我看了看&#xff0c;任務編輯器swGameTaskEditor 在寫入文件的時候&#xff0c;也不知道為什么有的文件就是UTF-8BOM格式&#xff1b;但一般都…

JQuery對象與DOM對象的區別與轉換

1.jQuery對象和DOM對象的區別 DOM對象&#xff0c;即是我們用傳統的方法(javascript)獲得的對象&#xff0c;jQuery對象即是用jQuery類庫的選擇器獲得的對象; eg: var domObj document.getElementById("id"); //DOM對象var $obj $("#id"); //jQuery對象;…

halcon append_ocr_trainf 將字符添加到訓練文件中

目錄append_ocr_trainf&#xff08;算子&#xff09;描述參數append_ocr_trainf&#xff08;算子&#xff09; append_ocr_trainf - 將字符添加到訓練文件中。 append_ocr_trainf&#xff08;Character&#xff0c;Image :: Class&#xff0c;TrainingFile ? 描述 運算符a…

CCD 尺寸

CCD&#xff08;包括CMOS感光元件&#xff09;的面積是按其矩形對角線英寸長度為指標的。這和定義電視屏幕尺寸類似。一英寸是25.4毫米。1/2.0英寸、1/1.8都是指CCD 對角線有多少分之一英寸長&#xff0c;分母小的其分數值就大&#xff0c;相應感光元件面積也大。 1/2.…

Quagga的安裝碰到的問題

1.如果出現以下錯誤&#xff1a; vtysh: symbol lookup error: /usr/local/lib/libreadline.so.6: undefined symbol: UP 解決方法如下: 1.rootlocalhost:~ # cd /usr/local/lib 2.rootlocalhost:/usr/local/lib# ls -la libreadline* 3.rootlocalhost:/usr/local/lib# mkd…