windows 多線程: CreateThread、_beginthread、_beginthreadex、AfxBeginThread 的區別


推薦參考博客秒殺多線程第二篇 多線程第一次親密接觸 CreateThread與_beginthreadex本質區別

CreateThread:Windows的API函數(SDK函數的標准形式,直截了當的創建方式,任何場合都可以使用),提供操作系統級別的創建線程的操作,且僅限於工作者線程

 

beginthread beginthreadex:MS對C Runtime庫的擴展SDK函數,首先針對C Runtime庫做了一些初始化的工作,以保證C Runtime庫工作正常,然后,調用CreateThread真正創建線程。beginthread是_beginthreadex的功能子集,雖然_beginthread內部是調用_beginthreadex但他屏蔽了象安全特性這樣的功能,例如,如果使用_beginthread,就無法創建帶有安全屬性的新線程,無法創建暫停的線程,也無法獲得線程的ID值。_beginthread與CreateThread不是同等級別,_beginthreadex和CreateThread在功能上完全可替代

AfxBeginThread:MFC中線程創建的MFC函數,它簡化了操作或讓線程能夠響應消息,即可用於界面線程,也可以用於工作者線程,但要注意盡量不要在一個MFC程序中使用_beginthreadex()或CreateThread()。

 

AfxBeginThread、BeginThread和BeginThreadex實際上是編譯器對CreateThread的封裝

 

.編程的時候如何選擇各個函數

1 MFC程序選擇AfxBeginThread當然不容置疑

 

2 如果不使用Microsoft的Visual   C++編譯器,你的編譯器供應商有它自己的CreateThred替代函數

 

3 盡量不要調用CreateThread。相反,應該使用Visual C++運行期庫函數_beginthreadex,原因如下:

  考慮標准C運行時庫的一些變量和函數,如errno,這是一個全局變量。全局變量用於多線程會出什么事,你一定知道的了。故必須存在一種機制,使得每個線程能夠引用它自己的errno變量,又不觸及另一線程的errno變量._beginthreadex就為每個線程分配自己的tiddata內存結構。該結構保存了許多像errno這樣的變量和函數的值、地址(自己看去吧)。  
  通過線程局部存儲將tiddata與線程聯系起來。具體實現在Threadex.c中有。  
  結束線程使用函數_endthreadex函數,釋放掉線程的tiddata數據塊。  
  CRT的函數庫在線程出現之前就已經存在,所以原有的CRT不能真正支持線程,這導致我們在編程的時候有了CRT庫的選擇,在MSDN中查閱CRT的函數時都有:  
  Libraries  
  LIBC.LIB   Single   thread   static   library,   retail   version    
  LIBCMT.LIB   Multithread   static   library,   retail   version    
  MSVCRT.LIB   Import   library   for   MSVCRT.DLL,   retail   version    
  這樣的提示!  
  對於線程的支持是后來的事!  
  這也導致了許多CRT的函數在多線程的情況下必須有特殊的支持,不能簡單的使用CreateThread就OK。  
  大多的CRT函數都可以在CreateThread線程中使用,看資料說只有signal()函數不可以,會導致進程終止!但可以用並不是說沒有問題!  
  有些CRT的函數象malloc(),   fopen(),   _open(),   strtok(),   ctime(),   或localtime()等函數需要專門的線程局部存儲的數據塊,這個數據塊通常需要在創建線程的時候就建立,如果使用CreateThread,這個數據塊就沒有建立,然后會怎樣呢?在這樣的線程中還是可以使用這些函數而且沒有出錯,實際上函數發現這個數據塊的指針為空時,會自己建立一個,然后將其與線程聯系在一起,這意味着如果你用CreateThread來創建線程,然后使用這樣的函數,會有一塊內存在不知不覺中創建,遺憾的是,這些函數並不將其刪除,而CreateThread和ExitThread也無法知道這件事,於是就會有Memory leak,在線程頻繁啟動的軟件中(比如某些服務器軟件),遲早會讓系統的內存資源耗盡!  
  _beginthreadex和_endthreadex就對這個內存塊做了處理,所以沒有問題!(不會有人故意用CreateThread創建然后用_endthreadex終止吧,而且線程的終止最好不要顯式的調用終止函數,自然退出最好!) 

  如果在除主線程之外的任何線程中進行一下操作,你就應該使用多線程版本的C runtime library,並使用_beginthreadex和_endthreadex:
1 使用malloc()和free(),或是new和delete
2 使用stdio.h或io.h里面聲明的任何函數
3 使用浮點變量或浮點運算函數
4 調用任何一個使用了靜態緩沖區的runtime函數,比如:asctime(),strtok()或rand()
 
  Handle的問題,_beginthread的對應函數_endthread自動的調用了CloseHandle,而_beginthreadex的對應函數_endthreadex則沒有,所以CloseHandle無論如何都是要調用的不過_endthread可以幫你執行自己不必寫,其他兩種就需要自己寫!(Jeffrey   Richter強烈推薦盡量不用顯式的終止函數,用自然退出的方式,自然退出當然就一定要自己寫CloseHandle)


轉載自C++多線程實例(_beginThreadex創建多線程)

二解釋
      1)如果你正在編寫C/C++代碼,決不應該調用CreateThread。相反,應該使用VisualC++運行期庫函數_beginthreadex,推出也應該使用_endthreadex。如果不使用Microsoft的VisualC++編譯器,你的編譯器供應商有它自己的CreateThred替代函數。不管這個替代函數是什么,你都必須使用。

2)因為_beginthreadex和_endthreadex是CRT線程函數,所以必須注意編譯選項runtimelibaray的選擇,使用MT或MTD。

3) _beginthreadex函數的參數列表與CreateThread函數的參數列表是相同的,但是參數名和類型並不完全相同。這是因為Microsoft的C/C++運行期庫的開發小組認為,C/C++運行期函數不應該對Windows數據類型有任何依賴。_beginthreadex函數也像CreateThread那樣,返回新創建的線程的句柄。
下面是關於_beginthreadex的一些要點
            &8226;每個線程均獲得由C/C++運行期庫的堆棧分配的自己的tiddata內存結構。(tiddata結構位於Mtdll.h文件中的VisualC++源代碼中)。

      &8226;傳遞給_beginthreadex的線程函數的地址保存在tiddata內存塊中。傳遞給該函數的參數也保存在該數據塊中。

      &8226;_beginthreadex確實從內部調用CreateThread,因為這是操作系統了解如何創建新線程的唯一方法。

      &8226;當調用CreatetThread時,它被告知通過調用_threadstartex而不是pfnStartAddr來啟動執行新線程。還有,傳遞給線程函數的參數是tiddata結構而不是pvParam的地址。

      &8226;如果一切順利,就會像CreateThread那樣返回線程句柄。如果任何操作失敗了,便返回NULL。

4) _endthreadex的一些要點:
          &8226;C運行期庫的_getptd函數內部調用操作系統的TlsGetValue函數,該函數負責檢索調用線程的tiddata內存塊的地址。

    &8226;然后該數據塊被釋放,而操作系統的ExitThread函數被調用,以便真正撤消該線程。當然,退出代碼要正確地設置和傳遞。

5)雖然也提供了簡化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。

6)線程handle因為是內核對象,所以需要在最后closehandle。

7)更多的API:

HANDLE GetCurrentProcess();

HANDLE GetCurrentThread();

DWORD GetCurrentProcessId();

DWORD GetCurrentThreadId()。

DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);

BOOL SetThreadPriority(HANDLE hThread,int nPriority);

BOOL SetPriorityClass(GetCurrentProcess(),  IDLE_PRIORITY_CLASS);

BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread();
三注意
1)C++主線程的終止,同時也會終止所有主線程創建的子線程,不管子線程有沒有執行完畢。所以上面的代碼中如果不調用WaitForSingleObject,則2個子線程t1和t2可能並沒有執行完畢或根本沒有執行。
2)如果某線程掛起,然后有調用WaitForSingleObject等待該線程,就會導致死鎖。所以上面的代碼如果不調用resumethread,則會死鎖。

為什么要用C運行時庫的_beginthreadex代替操作系統的CreateThread來創建線程?

來源自自1999年7月MSJ雜志的《Win32 Q&A》欄目

你也許會說我一直用CreateThread來創建線程,一直都工作得好好的,為什么要用_beginthreadex來代替CreateThread,下面讓我來告訴你為什么。
回答一個問題可以有兩種方式,一種是簡單的,一種是復雜的。
如果你不願意看下面的長篇大論,那我可以告訴你簡單的答案:_beginthreadex在內部調用了CreateThread,在調用之前_beginthreadex做了很多的工作,從而使得它比CreateThread更安全。


_beginthreadex用法

頭文件:process.h

函數原型:unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );

  //第1個參數:安全屬性,NULL為默認安全屬性
       //第2個參數:指定線程堆棧的大小。如果為0,則線程堆棧大小和創建它的線程的相同。一般用0
       //第3個參數:指定線程函數的地址,也就是線程調用執行的函數地址(用函數名稱即可,函數名稱就表示地址,注意的是函數訪問方式一定是__stdcall,函數返回值一定是unsigned,函數參數一定是void*)
       //第4個參數:傳遞給線程的參數的指針,可以通過傳入對象的指針,在線程函數中再轉化為對應類的指針
       //第5個參數:線程初始狀態,0:立即運行;CREATE_SUSPEND:懸掛(如果出事狀態定義為懸掛,就要調用ResumeThread(HANDLE) 來激活線程的運行)
      //第6個參數:用於記錄線程ID的地址

 

使用舉例

 #include<string>
 #include<iostream>
 #include<process.h>
 #include<windows.h>
 using namespace std;

struct Arg//用來傳參給線程函數
{
    double d_;
    string str_;
    Arg(double dd, string ss):d_(dd), str_(ss){}
};

//線程綁定的函數返回值和參數是確定的,而且一定要__stdcall
unsigned __stdcall threadFun(void *)
{
    for(int i = 0; i < 10; i++)
        cout<<i<<endl;
    return 1;
}

//可以通過結構體來傳入參數
unsigned __stdcall threadFunArg(void *arglist)
{
    Arg *p = (Arg *)arglist;
    cout<<p->d_<<endl;
    cout<<p->str_<<endl;
    return 2;
}

//簡單的線程類
class ThreadClass
{
private:
    string str_;
    int i_;
public:
    ThreadClass(string s, int i):str_(s), i_(i){}
    static unsigned __stdcall threadStaic(void *arg)
    {
        ThreadClass *p = (ThreadClass *)arg;
        p->threadfun();
        return 3;
    }
    void threadfun()
    {
        cout<<str_<<endl;
        cout<<i_<<endl;
    }
};

int main()
{
    unsigned int thID1, thID2, thID3, thID4;
    HANDLE hth1, hth2, hth3, hth4;
    Arg arg(3.14, "hello world");
    ThreadClass tclass("welcom", 999);

    //注意的是_beginthreadex是立即返回的,系統不會等線程函數執行完畢,因此要保證
    //局部arg變量 在線程函數執行完畢前不會釋放,更安全的是使用new來構造arg
    hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID1);
    hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID2);
    hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunArg, &arg, 0, &thID3);
    hth4 = (HANDLE)_beginthreadex(NULL, 0, ThreadClass::threadStaic, &tclass, 0,
                                   &thID4);

    //主線程一定要等待子線程結束
    WaitForSingleObject(hth1, INFINITE);
    WaitForSingleObject(hth2, INFINITE);
    WaitForSingleObject(hth3, INFINITE);
    WaitForSingleObject(hth4, INFINITE);

    DWORD exitCode1, exitCode2, exitCode3, exitCode4;
    GetExitCodeThread(hth1, &exitCode1);
    GetExitCodeThread(hth2, &exitCode2);
    GetExitCodeThread(hth3, &exitCode3);
    GetExitCodeThread(hth4, &exitCode4);
    cout<<endl<<"exitcode::"<<exitCode1<<" "<<exitCode2<<" "<<exitCode3<<" "
        <<exitCode4<<endl;
    cout<<"ID:"<<thID1<<" "<<thID2<<" "<<thID3<<" "<<thID4<<endl;

    //一定要記得關閉線程句柄
    CloseHandle(hth1);
    CloseHandle(hth2);
    CloseHandle(hth3);
    CloseHandle(hth4);
}

 

【版權聲明】轉載請注明出處http://www.cnblogs.com/TenosDoIt/archive/2013/04/15/3022036.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM