一、原因分析
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()、tmpnam()、gmtime()、asctime()等函數也會遇到這種由多個線程訪問修改導致的數據覆蓋問題。
為了解決這個問題,Windows操作系統提供了這樣的一種解決方案——每個線程都將擁有自己專用的一塊內存區域來供標准C運行庫中所有有需要的函數使用。而且這塊內存區域的創建就是由C/C++運行庫函數_beginthreadex()來負責的。
_beginthreadex()函數在創建新線程時會分配並初始化一個_tiddata塊。這個_tiddata塊自然是用來存放一些需要線程獨享的數據。新線程運行時會首先將_tiddata塊與自己進一步關聯起來。然后新線程調用標准C運行庫函數如strtok()時就會先取得_tiddata塊的地址再將需要保護的數據存入_tiddata塊中。這樣每個線程就只會訪問和修改自己的數據而不會去篡改其它線程的數據了。因此,如果在代碼中有使用標准C運行庫中的函數時,盡量使用_beginthreadex()來代替CreateThread()
二、實例
下面的例子使用_beginthreadex()來創建線程。
#include<process.h>
#include<windows.h>
#include<iostream>
using namespace std;
unsigned int __stdcall ThreadFun(PVOID pM)
{
printf("線程ID 為 %d 的子線程輸出: Hello World\n", GetCurrentThreadId());
return 0;
}
int main()
{
const int THREAD_NUM = 5;
HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++)
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
return 0;
}
運行結果: