1. 程式人生 > >秒殺多執行緒第一篇 CreateThread與_beginthreadex本質區別

秒殺多執行緒第一篇 CreateThread與_beginthreadex本質區別

   本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用CreateThread還是_beginthreadex

   使用多執行緒其實是非常容易的,下面這個程式的主執行緒會建立了一個子執行緒並等待其執行完畢,子執行緒就輸出它的執行緒ID號然後輸出一句經典名言——Hello World。整個程式的程式碼非常簡短,只有區區幾行。

  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.     HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL);  
  16.     WaitForSingleObject(handle, INFINITE);  
  17.     return 0;  
  18. }  

執行結果如下所示:

下面來細講下程式碼中的一些函式

第一個 CreateThread

函式功能:建立執行緒

函式原型:

HANDLEWINAPICreateThread(

LPSECURITY_ATTRIBUTESlpThreadAttributes,

SIZE_TdwStackSize,

 LPTHREAD_START_ROUTINE

lpStartAddress,

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。很多執行庫中的函式在出錯時會將錯誤代號賦值給這個全域性變數,這樣可以方便除錯。但如果有這樣的一個程式碼片段:

  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()函式的區別。

  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.     // validation section 檢查initialcode是否為NULL
  16.     _VALIDATE_RETURN(initialcode != NULL, EINVAL, 0);  
  17.     //Initialize FlsGetValue function pointer
  18.     __set_flsgetvalue();  
  19.     //Allocate and initialize a per-thread data structure for the to-be-created thread.
  20.     //相當於new一個_tiddata結構,並賦給_ptiddata指標。
  21.     if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL )  
  22.         goto error_return;  
  23.     // Initialize the per-thread data
  24.     //初始化執行緒的_tiddata塊即CRT資料區域 見注2
  25.     _initptd(ptd, _getptd()->ptlocinfo);  
  26.     //設定_tiddata結構中的其它資料,這樣這塊_tiddata塊就與執行緒聯絡在一起了。
  27.     ptd->_initaddr = (void *) initialcode; //執行緒函式地址
  28.     ptd->_initarg = argument;              //傳入的執行緒引數
  29.     ptd->_thandle = (uintptr_t)(-1);  
  30. #if defined (_M_CEE) || defined (MRTDLL)
  31.     if(!_getdomain(&(ptd->__initDomain))) //見注3
  32.     {  
  33.         goto error_return;  
  34.     }  
  35. #endif  // defined (_M_CEE) || defined (MRTDLL)
  36.     // Make sure non-NULL thrdaddr is passed to CreateThread
  37.     if ( thrdaddr == NULL )//判斷是否需要返回執行緒ID號
  38.         thrdaddr = &dummyid;  
  39.     // Create the new thread using the parameters supplied by the caller.
  40.     //_beginthreadex()最終還是會呼叫CreateThread()來向系統申請建立執行緒
  41.     if ( (thdl = (uintptr_t)CreateThread(  
  42.                     (LPSECURITY_ATTRIBUTES)security,  
  43.                     stacksize,  
  44.                     _threadstartex,  
  45.                     (LPVOID)ptd,  
  46.                     createflag,  
  47.                     (LPDWORD)thrdaddr))  
  48.         == (uintptr_t)0 )  
  49.     {  
  50.         err = GetLastError();  
  51.         goto error_return;  
  52.     }  
  53.     //Good return
  54.     return(thdl); //執行緒建立成功,返回新執行緒的控制代碼.
  55.     //Error return
  56. error_return:  
  57.     //Either ptd is NULL, or it points to the no-longer-necessary block
  58.     //calloc-ed for the _tiddata struct which should now be freed up.
  59.     //回收由_calloc_crt()申請的_tiddata塊
  60.     _free_crt(ptd);  
  61.     // Map the error, if necessary.
  62.     // Note: this routine returns 0 for failure, just like the Win32
  63.     // API CreateThread, but _beginthread() returns -1 for failure.
  64.     //校正錯誤代號(可以呼叫GetLastError()得到錯誤代號)
  65.     if ( err != 0L )  
  66.         _dosmaperr(err);  
  67.     return( (uintptr_t)0 ); //返回值為NULL的效控制代碼
  68. }  

講解下部分程式碼:

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);

註釋中的CRT C 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()來建立多個子執行緒:

  1. //建立多子個執行緒例項
  2. #include <stdio.h>
  3. #include <process.h>
  4. 相關推薦

    執行第一 CreateThread_beginthreadex本質區別

       本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用CreateThread還是_

    執行第一 執行筆試面試題彙總

        系列前言    本系列是本人蔘加微軟亞洲研究院,騰訊研究院,迅雷面試時整理的,另外也加入一些其它IT公司如百度,阿里巴巴的筆試面試題目,因此具有很強的針對性。系列中不但會詳細講解多執行緒同步互斥的各種“招式”,而且會進一步的講解多執行緒同步互斥的“內功心法”。有了“招

    執行第二 執行第一次親密接觸 CreateThread_beginthreadex本質區別

    本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用C

    執行第二 執行第一次親密接觸 CreateThread beginthreadex本質區別

                        本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用

    執行第八 經典執行同步 訊號量Semaphore

    前面介紹了關鍵段CS、事件Event、互斥量Mutex在經典執行緒同步問題中的使用。本篇介紹用訊號量Semaphore來解決這個問題。 首先也來看看如何使用訊號量,訊號量Semaphore常用有三個函式,使用很方便。下面是這幾個函式的原型和使用說明。 第一個 Create

    執行第七 經典執行同步 互斥量Mutex

    閱讀本篇之前推薦閱讀以下姊妹篇: 《秒殺多執行緒第四篇一個經典的多執行緒同步問題》 《秒殺多執行緒第五篇經典執行緒同步關鍵段CS》 《秒殺多執行緒第六篇經典執行緒同步事件Event》   前面介紹了關鍵段CS、事件Event在經典執行緒同步問題中的使用。本篇介紹用互斥量Mu

    執行第四 一個經典的執行同步問題

    上一篇《秒殺多執行緒第三篇原子操作 Interlocked系列函式》中介紹了原子操作在多程序中的作用,現在來個複雜點的。這個問題涉及到執行緒的同步和互斥,是一道非常有代表性的多執行緒同步問題,如果能將這個問題搞清楚,那麼對多執行緒同步也就打下了良好的基礎。程式描述:主執行緒啟

    執行第三 原子操作 Interlocked系列函式

    上一篇《多執行緒第一次親密接觸 CreateThread與_beginthreadex本質區別》中講到一個多執行緒報數功能。為了描述方便和程式碼簡潔起見,我們可以只輸出最後的報數結果來觀察程式是否執行出錯。這也非常類似於統計一個網站每天有多少使用者登入,每個使用者登入用一個執

    執行第九 經典執行同步總結 關鍵段 事件 互斥量 訊號量

                    前面《秒殺多執行緒第四篇一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,這個問題包括了主執行緒與子執行緒的同步,子執行緒間的互斥,是一道非常經典的多執行緒同步互斥問題範例,後面分別用了四篇來詳細介紹常用的執行緒同步互斥機制——關鍵段、事件、互斥量、訊號量。下面

    執行第五 經典執行同步 關鍵段CS

                    上一篇《秒殺多執行緒第四篇 一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。本文首先介紹下如何使用關鍵段,然後再深層次的分析下關鍵段的實現機制與原理。關鍵段CRITICAL_SECTION一共就

    執行第十一 讀者寫者問題

                    與上一篇《秒殺多執行緒第十篇 生產者消費者問題》的生產者消費者問題一樣,讀者寫者也是一個非常著名的同步問題。讀者寫者問題描述非常簡單,有一個寫者很多讀者,多個讀者可以同時讀檔案,但寫者在寫檔案時不允許有讀者在讀檔案,同樣有讀者在讀檔案時寫者也不去能寫檔案。上面是讀者寫者問題示意

    執行總結第二 執行第一次親密接觸 CreateThread_beginthreadex本質區別

    本文將帶領你與多執行緒作第一次親密接觸,並深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文後你能輕鬆的使用多執行緒並能流暢準確的回答CreateThread與_beginthreadex到底有什麼區別,在實際的程式設計中到底應該使用Cre

    執行第五---經典執行同步 關鍵段(臨界區)CS

    上一篇《秒殺多執行緒第四篇 一個經典的多執行緒同步問題》提出了一個經典的多執行緒同步互斥問題,本篇將用關鍵段CRITICAL_SECTION來嘗試解決這個問題。 本文首先介紹下如何使用關鍵段,然後再深層次的分析下關鍵段的實現機制與原理。 關鍵段CRITICA

    執行第十 生產者消費者問題

        繼經典執行緒同步問題之後,我們來看看生產者消費者問題及讀者寫者問題。生產者消費者問題是一個著名的執行緒同步問題,該問題描述如下:有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,為了使生產者和消費者能併發執行,在兩者之間設定一個具有多個緩衝區的緩衝池,生產者

    java執行第一——執行基礎知識點總結

    程序與執行緒一個正在作業系統中執行的 exe 程式理解成一個“程序”,執行緒可以理解成是在程序中獨立執行的子任務。一個QQ.exe 執行時是一個程序, 其中:好友視訊執行緒、下載檔案執行緒、傳輸資料執行緒、傳送表 情執行緒等。  2.main方法是一個執行緒3.執行緒是一個子

    C++執行-第一-Atomic-原子操作

    此係列基於Boost庫多執行緒,但是大部分都在C++11中已經實現,所以兩者基本一致。沒什麼特殊要求,練手還是C++11吧,方便不用配置。 PS:Boost不愧為C++準標準庫。 本來不打算寫,畢竟都是書上的內容,但是後來發現查書太麻煩,所以動手寫了這個系列,幫助我只看程式

    執行系列(摘錄)

    CreateThread與_beginthreadex本質區別 儘量使用_beginthreadex()來代替使用CreateThread() 像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函式也會遇到這種由

    執行第一:使用_beginthreadex建立執行

    我們先來了解一下執行緒的相關函式:CreateThread函式:HANDLE WINAPI CreateThread ( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes , // pointer to security attributes _I

    【轉載】CreateThread_beginthreadex本質區別

    wmi ted 函數返回值 rar turn 問題 初始化 控制 switch 轉載文章,原文地址:http://blog.csdn.net/morewindows/article/details/7421759 本文將帶領你與多線程作第一次親密接觸,並深入分析Cr

    執行併發——三件兵器

      筆者是廣州的java程式設計師,剛畢業半年,工作之餘寫部落格,如果覺得我的文章寫得不錯,可以關注我的微信公眾號(J2彬彬),裡面會有更多精彩內容。從2018年8月份開始寫部落格,希望日後寫出更多通俗易懂的技術文章與大家一同分享。 talk is cheap,show me the c