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的封裝

 

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

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

 

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

 

儘量不要調用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的地址

 

使用舉例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#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

發佈了10 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章