1. 程式人生 > >01創建線程CreateThread和_beginthreadex

01創建線程CreateThread和_beginthreadex

通過 won sbt bof mui bcm void .exe avg

Windows多線程之線程創建

一. 線程創建函數 CreateThread

1. 函數原型
    HANDLE WINAPI CreateThread(
      _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,   
      _In_      SIZE_T                 dwStackSize,
      _In_      LPTHREAD_START_ROUTINE lpStartAddress,
      _In_opt_  LPVOID                 lpParameter,
      _In_      DWORD                  dwCreationFlags,
      _Out_opt_ LPDWORD                lpThreadId
    );
2, 參數說明
  • 第一個參數 lpThreadAttributes 表示線程內核對象的安全屬性,一般傳入 NULL 表示使用默認設置。

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

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

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

  • 第五個參數 dwCreationFlags 指定額外的標誌來控制線程的創建,為 0 表示線程創建之後立刻就進行調度,如果為 CREAATE_SUSPENDED 則表示線程創建後暫停運行,這樣它就無法調度,直到調用 ResumeThread()。

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

3. 返回值

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

4. 示例代碼
#include <windows.h>
#include <stdio.h>

DWORD WINAPI ThreadFunc(LPVOID);

int main()
{
    HANDLE hThread;
    DWORD threadID;
    printf("Enter the main thread ...\n");
    hThread = CreateThread(NULL, 0, ThreadFunc, 0, 0, NULL);
    for (int i = 0; i < 2; i++)
    {
        printf("I am the main thread,PID = %d\n", GetCurrentThreadId());
        Sleep(1000);
    }
    printf("Quit the main thread ...\n");
    return 0;
}

DWORD WINAPI ThreadFunc(LPVOID)
{
    printf("Enter the child thread ...\n");
    for (int i = 0; i < 2; i++)
    {
        printf("I am child thread,PID = %d\n", GetCurrentThreadId());
        Sleep(500);
    }
    printf("Quit the child thread ...\n");
    return 0;
}

執行結果如下:

技術分享圖片

二. 線程創建函數 _beginthreadex()

1. 概述

? CreateThread() 函數是 Windows 提供的 API 接口,在 C/C++ 語言中有另一個創建線程的函數 _beginthreadex(),我們應該盡量使用 _beginthreadex() 來代替使用 CreateThread(),因為它比 CreateThread 更安全。

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

if (system("notepad.exe readme.txt") == -1)  
{  
    switch(errno)  
    {  
        ...//錯誤處理代碼  
    }  
} 

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

? 為了解決這個問題,Windows 操作系統提供了這樣一種解決方案 —— 每個線程都將擁有自己專用的一塊內存區域來提供標準 C 運行庫中所有有需要的函數使用。而且這塊內存區域的創建就是由 C/C++ 運行庫函數 _beginthreadex() 來負責的。 _beginthreadex() 函數在創建新線程時會分配並初始化一個 _tiddata 塊。這個 _tiddata 塊自然是用來存放一些需要線程獨享的數據。新線程運行時會首先將 _tiddata 塊與自己進一步關聯起來。然後新線程調用標準 C 運行庫函數如 strtok() 時就會先取得 _tiddata 塊的地址再講需要保護的數據存入 _tiddata 塊中。這樣每個線程就只會訪問和修改自己的數據而不會去篡改其他線程的數據了。因此,如果在代碼中有使用標準 C 運行庫中的函數時,盡量時用 _beginthreadex() 來代替 CreateThread()。

2. 函數原型
unsigned long _beginthreadex(void* security,
                            unsigned stack_size,
                            unsigned(_stdcall *start_address)(void*),
                            void* arglist,
                            unsigned initflag,
                            unsigned* thrdaddr
                            );
3. 參數說明
  • 第一個參數 security 表示安全屬性,NULL 為默認安全屬性。

  • 第二個參數 stack_size 指定線程堆棧的大小。如果為 0 ,則線程堆棧大小和創建它的線程的相同。一般用 0 。

  • 第三個參數 start_address 指定線程函數的地址,也就是線程調用執行的函數地址(用函數名稱即可,函數名稱就表示地址)。

  • 第四個參數 arglist 表示傳遞給線程的參數的指針,可以通過傳入對象的指針,在線程函數中再轉化為對應類的指針。

  • 第五個參數 initflag 表示線程初始化狀態,0 表示立即運行;CREATE_SUSPEND 表示懸掛。

  • 第六個參數 thrdaddr 用於記錄線程 ID 的地址。

4. 返回值

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

5. 示例代碼
#include <Windows.h>
#include <process.h>
#include <stdio.h>

unsigned int _stdcall ThreadFunc(PVOID pParam)
{
    printf("the child thread %d: Hello world!\n", GetCurrentThreadId());
    return 0;
}

int main()
{
    const int THREAD_MUN = 5;
    HANDLE handles[THREAD_MUN];
    for (int i = 0; i < THREAD_MUN; i++)
    {
        handles[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL);
    }
    WaitForMultipleObjects(THREAD_MUN, handles, TRUE, INFINITE);
    return 0;
}

執行結果如下:

技術分享圖片

01創建線程CreateThread和_beginthreadex