本文將帶領(lǐng)你與多線程作第一次親密接觸,并深入分析CreateThread與_beginthreadex的本質(zhì)區(qū)別,相信閱讀本文后你能輕松的使用多線程并能流暢準(zhǔn)確的回答CreateThread與_beginthreadex到底有什么區(qū)別,在實(shí)際的編程中到底應(yīng)該使用CreateThread還是_beginthreadex?
使用多線程其實(shí)是非常容易的,下面這個(gè)程序的主線程會(huì)創(chuàng)建了一個(gè)子線程并等待其運(yùn)行完畢,子線程就輸出它的線程ID號(hào)然后輸出一句經(jīng)典名言——Hello World。整個(gè)程序的代碼非常簡(jiǎn)短,只有區(qū)區(qū)幾行。
[cpp]view plaincopy
//最簡(jiǎn)單的創(chuàng)建多線程實(shí)例
#include
#include
//子線程函數(shù)
DWORDWINAPIThreadFun(LPVOIDpM)
{
printf("子線程的線程ID號(hào)為:%d\n子線程輸出HelloWorld\n",GetCurrentThreadId());
return0;
}
//主函數(shù),所謂主函數(shù)其實(shí)就是主線程執(zhí)行的函數(shù)。
intmain()
{
printf("最簡(jiǎn)單的創(chuàng)建多線程實(shí)例\n");
printf("--byMoreWindows(http://blog.csdn.net/MoreWindows)--\n\n");
HANDLEhandle=CreateThread(NULL,0,ThreadFun,NULL,0,NULL);
WaitForSingleObject(handle,INFINITE);
return0;
}
運(yùn)行結(jié)果如下所示:
下面來(lái)細(xì)講下代碼中的一些函數(shù)
第一個(gè)CreateThread
函數(shù)功能:創(chuàng)建線程
函數(shù)原型:
HANDLEWINAPICreateThread(
LPSECURITY_ATTRIBUTESlpThreadAttributes,
SIZE_TdwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
LPDWORDlpThreadId
);
函數(shù)說(shuō)明:
第一個(gè)參數(shù)表示線程內(nèi)核對(duì)象的安全屬性,一般傳入NULL表示使用默認(rèn)設(shè)置。
第二個(gè)參數(shù)表示線程棧空間大小。傳入0表示使用默認(rèn)大?。?MB)。
第三個(gè)參數(shù)表示新線程所執(zhí)行的線程函數(shù)地址,多個(gè)線程可以使用同一個(gè)函數(shù)地址。
第四個(gè)參數(shù)是傳給線程函數(shù)的參數(shù)。
第五個(gè)參數(shù)指定額外的標(biāo)志來(lái)控制線程的創(chuàng)建,為0表示線程創(chuàng)建之后立即就可以進(jìn)行調(diào)度,如果為CREATE_SUSPENDED則表示線程創(chuàng)建后暫停運(yùn)行,這樣它就無(wú)法調(diào)度,直到調(diào)用ResumeThread()。
第六個(gè)參數(shù)將返回線程的ID號(hào),傳入NULL表示不需要返回該線程ID號(hào)。
函數(shù)返回值:
成功返回新線程的句柄,失敗返回NULL。
第二個(gè)WaitForSingleObject
函數(shù)功能:等待函數(shù)–使線程進(jìn)入等待狀態(tài),直到指定的內(nèi)核對(duì)象被觸發(fā)。
函數(shù)原形:
DWORDWINAPIWaitForSingleObject(
HANDLEhHandle,
DWORDdwMilliseconds
);
函數(shù)說(shuō)明:
第一個(gè)參數(shù)為要等待的內(nèi)核對(duì)象。
第二個(gè)參數(shù)為最長(zhǎng)等待的時(shí)間,以毫秒為單位,如傳入5000就表示5秒,傳入0就立即返回,傳入INFINITE表示無(wú)限等待。
因?yàn)榫€程的句柄在線程運(yùn)行時(shí)是未觸發(fā)的,線程結(jié)束運(yùn)行,句柄處于觸發(fā)狀態(tài)。所以可以用WaitForSingleObject()來(lái)等待一個(gè)線程結(jié)束運(yùn)行。
函數(shù)返回值:
在指定的時(shí)間內(nèi)對(duì)象被觸發(fā),函數(shù)返回WAIT_OBJECT_0。超過(guò)最長(zhǎng)等待時(shí)間對(duì)象仍未被觸發(fā)返回WAIT_TIMEOUT。傳入?yún)?shù)有錯(cuò)誤將返回WAIT_FAILED
CreateThread()函數(shù)是Windows提供的API接口,在C/C++語(yǔ)言另有一個(gè)創(chuàng)建線程的函數(shù)_beginthreadex(),在很多書上(包括《Windows核心編程》)提到過(guò)盡量使用_beginthreadex()來(lái)代替使用CreateThread(),這是為什么了?下面就來(lái)探索與發(fā)現(xiàn)它們的區(qū)別吧。
首先要從標(biāo)準(zhǔn)C運(yùn)行庫(kù)與多線程的矛盾說(shuō)起,標(biāo)準(zhǔn)C運(yùn)行庫(kù)在1970年被實(shí)現(xiàn)了,由于當(dāng)時(shí)沒(méi)任何一個(gè)操作系統(tǒng)提供對(duì)多線程的支持。因此編寫標(biāo)準(zhǔn)C運(yùn)行庫(kù)的程序員根本沒(méi)考慮多線程程序使用標(biāo)準(zhǔn)C運(yùn)行庫(kù)的情況。比如標(biāo)準(zhǔn)C運(yùn)行庫(kù)的全局變量errno。很多運(yùn)行庫(kù)中的函數(shù)在出錯(cuò)時(shí)會(huì)將錯(cuò)誤代號(hào)賦值給這個(gè)全局變量,這樣可以方便調(diào)試。但如果有這樣的一個(gè)代碼片段:
[cpp]view plaincopy
if(system("notepad.exereadme.txt")==-1)
{
switch(errno)
{
...//錯(cuò)誤處理代碼
}
}
假設(shè)某個(gè)線程A在執(zhí)行上面的代碼,該線程在調(diào)用system()之后且尚未調(diào)用switch()語(yǔ)句時(shí)另外一個(gè)線程B啟動(dòng)了,這個(gè)線程B也調(diào)用了標(biāo)準(zhǔn)C運(yùn)行庫(kù)的函數(shù),不幸的是這個(gè)函數(shù)執(zhí)行出錯(cuò)了并將錯(cuò)誤代號(hào)寫入全局變量errno中。這樣線程A一旦開始執(zhí)行switch()語(yǔ)句時(shí),它將訪問(wèn)一個(gè)被B線程改動(dòng)了的errno。這種情況必須要加以避免!因?yàn)椴粏螁问沁@一個(gè)變量會(huì)出問(wèn)題,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函數(shù)也會(huì)遇到這種由多個(gè)線程訪問(wèn)修改導(dǎo)致的數(shù)據(jù)覆蓋問(wèn)題。
為了解決這個(gè)問(wèn)題,Windows操作系統(tǒng)提供了這樣的一種解決方案——每個(gè)線程都將擁有自己專用的一塊內(nèi)存區(qū)域來(lái)供標(biāo)準(zhǔn)C運(yùn)行庫(kù)中所有有需要的函數(shù)使用。而且這塊內(nèi)存區(qū)域的創(chuàng)建就是由C/C++運(yùn)行庫(kù)函數(shù)_beginthreadex()來(lái)負(fù)責(zé)的。下面列出_beginthreadex()函數(shù)的源代碼(我在這份代碼中增加了一些注釋)以便讀者更好的理解_beginthreadex()函數(shù)與CreateThread()函數(shù)的區(qū)別。
[cpp]view plaincopy
//_beginthreadex源碼整理ByMoreWindows(http://blog.csdn.net/MoreWindows)
_MCRTIMPuintptr_t__cdecl_beginthreadex(
void*security,
unsignedstacksize,
unsigned(__CLR_OR_STD_CALL*initialcode)(void*),
void*argument,
unsignedcreateflag,
unsigned*thrdaddr
)
{
_ptiddataptd;//pointertoper-threaddata見(jiàn)注1
uintptr_tthdl;//threadhandle線程句柄
unsignedlongerr=0L;//ReturnfromGetLastError()
unsigneddummyid;//dummyreturnedthreadID線程ID號(hào)
//validationsection檢查initialcode是否為NULL
_VALIDATE_RETURN(initialcode!=NULL,EINVAL,0);
//InitializeFlsGetValuefunctionpointer
__set_flsgetvalue();
//Allocateandinitializeaper-threaddatastructurefortheto-be-createdthread.
//相當(dāng)于new一個(gè)_tiddata結(jié)構(gòu),并賦給_ptiddata指針。
if((ptd=(_ptiddata)_calloc_crt(1,sizeof(struct_tiddata)))==NULL)
gotoerror_return;
//Initializetheper-threaddata
//初始化線程的_tiddata塊即CRT數(shù)據(jù)區(qū)域見(jiàn)注2
_initptd(ptd,_getptd()->ptlocinfo);
//設(shè)置_tiddata結(jié)構(gòu)中的其它數(shù)據(jù),這樣這塊_tiddata塊就與線程聯(lián)系在一起了。
ptd->_initaddr=(void*)initialcode;//線程函數(shù)地址
ptd->_initarg=argument;//傳入的線程參數(shù)
ptd->_thandle=(uintptr_t)(-1);
#ifdefined(_M_CEE)||defined(MRTDLL)
if(!_getdomain(&(ptd->__initDomain)))//見(jiàn)注3
{
gotoerror_return;
}
#endif//defined(_M_CEE)||defined(MRTDLL)
//Makesurenon-NULLthrdaddrispassedtoCreateThread
if(thrdaddr==NULL)//判斷是否需要返回線程ID號(hào)
thrdaddr=&dummyid;
//Createthenewthreadusingtheparameterssuppliedbythecaller.
//_beginthreadex()最終還是會(huì)調(diào)用CreateThread()來(lái)向系統(tǒng)申請(qǐng)創(chuàng)建線程
if((thdl=(uintptr_t)CreateThread(
(LPSECURITY_ATTRIBUTES)security,
stacksize,
_threadstartex,
(LPVOID)ptd,
createflag,
(LPDWORD)thrdaddr))
==(uintptr_t)0)
{
err=GetLastError();
gotoerror_return;
}
//Goodreturn
return(thdl);//線程創(chuàng)建成功,返回新線程的句柄.
//Errorreturn
error_return:
//EitherptdisNULL,oritpointstotheno-longer-necessaryblock
//calloc-edforthe_tiddatastructwhichshouldnowbefreedup.
//回收由_calloc_crt()申請(qǐng)的_tiddata塊
_free_crt(ptd);
//Maptheerror,ifnecessary.
//Note:thisroutinereturns0forfailure,justliketheWin32
//APICreateThread,but_beginthread()returns-1forfailure.
//校正錯(cuò)誤代號(hào)(可以調(diào)用GetLastError()得到錯(cuò)誤代號(hào))
if(err!=0L)
_dosmaperr(err);
return((uintptr_t)0);//返回值為NULL的效句柄
}
講解下部分代碼:
注1._ptiddataptd;中的_ptiddata是個(gè)結(jié)構(gòu)體指針。在mtdll.h文件被定義:
typedefstruct_tiddata*_ptiddata
微軟對(duì)它的注釋為Structure for each thread's data。這是一個(gè)非常大的結(jié)構(gòu)體,有很多成員。本文由于篇幅所限就不列出來(lái)了。
注2._initptd(ptd,_getptd()->ptlocinfo);微軟對(duì)這一句代碼中的getptd()的說(shuō)明為:
/* return address of per-thread CRT data */
_ptiddata__cdecl_getptd(void);
對(duì)_initptd()說(shuō)明如下:
/* initialize a per-thread CRT data block */
void__cdecl_initptd(_Inout_ _ptiddata_Ptd,_In_opt_ pthreadlocinfo_Locale);
注釋中的CRT(C Runtime Library)即標(biāo)準(zhǔn)C運(yùn)行庫(kù)。
注3.if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函數(shù)代碼可以在thread.c文件中找到,其主要功能是初始化COM環(huán)境。
由上面的源代碼可知,_beginthreadex()函數(shù)在創(chuàng)建新線程時(shí)會(huì)分配并初始化一個(gè)_tiddata塊。這個(gè)_tiddata塊自然是用來(lái)存放一些需要線程獨(dú)享的數(shù)據(jù)。事實(shí)上新線程運(yùn)行時(shí)會(huì)首先將_tiddata塊與自己進(jìn)一步關(guān)聯(lián)起來(lái)。然后新線程調(diào)用標(biāo)準(zhǔn)C運(yùn)行庫(kù)函數(shù)如strtok()時(shí)就會(huì)先取得_tiddata塊的地址再將需要保護(hù)的數(shù)據(jù)存入_tiddata塊中。這樣每個(gè)線程就只會(huì)訪問(wèn)和修改自己的數(shù)據(jù)而不會(huì)去篡改其它線程的數(shù)據(jù)了。因此,如果在代碼中有使用標(biāo)準(zhǔn)C運(yùn)行庫(kù)中的函數(shù)時(shí),盡量使用_beginthreadex()來(lái)代替CreateThread()。相信閱讀到這里時(shí),你會(huì)對(duì)這句簡(jiǎn)短的話有個(gè)非常深刻的印象,如果有面試官問(wèn)起,你也可以流暢準(zhǔn)確的回答了^_^。
接下來(lái),類似于上面的程序用CreateThread()創(chuàng)建輸出“Hello World”的子線程,下面使用_beginthreadex()來(lái)創(chuàng)建多個(gè)子線程:
[cpp]view plaincopy
//創(chuàng)建多子個(gè)線程實(shí)例
#include
#include
#include
//子線程函數(shù)
unsignedint__stdcallThreadFun(PVOIDpM)
{
printf("線程ID號(hào)為%4d的子線程說(shuō):HelloWorld\n",GetCurrentThreadId());
return0;
}
//主函數(shù),所謂主函數(shù)其實(shí)就是主線程執(zhí)行的函數(shù)。
intmain()
{
printf("創(chuàng)建多個(gè)子線程實(shí)例\n");
printf("--byMoreWindows(http://blog.csdn.net/MoreWindows)--\n\n");
constintTHREAD_NUM=5;
HANDLEhandle[THREAD_NUM];
for(inti=0;i
handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL);
WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);
return0;
}
運(yùn)行結(jié)果如下:
圖中每個(gè)子線程說(shuō)的都是同一句話,不太好看。能不能來(lái)一個(gè)線程報(bào)數(shù)功能,即第一個(gè)子線程輸出1,第二個(gè)子線程輸出2,第三個(gè)子線程輸出3,……。要實(shí)現(xiàn)這個(gè)功能似乎非常簡(jiǎn)單——每個(gè)子線程對(duì)一個(gè)全局變量進(jìn)行遞增并輸出就可以了。代碼如下:
[cpp]view plaincopy
//子線程報(bào)數(shù)
#include
#include
#include
intg_nCount;
//子線程函數(shù)
unsignedint__stdcallThreadFun(PVOIDpM)
{
g_nCount++;
printf("線程ID號(hào)為%4d的子線程報(bào)數(shù)%d\n",GetCurrentThreadId(),g_nCount);
return0;
}
//主函數(shù),所謂主函數(shù)其實(shí)就是主線程執(zhí)行的函數(shù)。
intmain()
{
printf("子線程報(bào)數(shù)\n");
printf("--byMoreWindows(http://blog.csdn.net/MoreWindows)--\n\n");
constintTHREAD_NUM=10;
HANDLEhandle[THREAD_NUM];
g_nCount=0;
for(inti=0;i
handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL);
WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);
return0;
}
對(duì)一次運(yùn)行結(jié)果截圖如下:
顯示結(jié)果從1數(shù)到10,看起來(lái)好象沒(méi)有問(wèn)題。
答案是不對(duì)的,雖然這種做法在邏輯上是正確的,但在多線程環(huán)境下這樣做是會(huì)產(chǎn)生嚴(yán)重的問(wèn)題。
-
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7615瀏覽量
137847 -
多線程
+關(guān)注
關(guān)注
0文章
278瀏覽量
20076
原文標(biāo)題:多線程CreateThread與_beginthreadex本質(zhì)區(qū)別
文章出處:【微信號(hào):C_Expert,微信公眾號(hào):C語(yǔ)言專家集中營(yíng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
FPGA與ARM的本質(zhì)區(qū)別是什么?
FPGA與ARM的本質(zhì)區(qū)別
多線程和多進(jìn)程的區(qū)別
筆記本的結(jié)構(gòu)深入分析
基于SWT的多線程解決方案
多核與多線程技術(shù)的區(qū)別
多線程好還是單線程好?單線程和多線程的區(qū)別 優(yōu)缺點(diǎn)分析
光纖和光纜的本質(zhì)區(qū)別是什么
分析unidbg(unidbgMutil)多線程機(jī)制
![<b class='flag-5'>分析</b>unidbg(unidbgMutil)<b class='flag-5'>多線程</b>機(jī)制](https://file1.elecfans.com/web2/M00/88/7C/wKgaomRokUGADyjdAAJ7d-pwE8w007.jpg)
線程是什么的基本單位 進(jìn)程與線程的本質(zhì)區(qū)別
聚徽觸控-工控機(jī)和商用電腦本質(zhì)區(qū)別是什么
Python中多線程和多進(jìn)程的區(qū)別
![Python中<b class='flag-5'>多線程</b>和多進(jìn)程的<b class='flag-5'>區(qū)別</b>](https://file1.elecfans.com/web2/M00/0A/EF/wKgaomcYcmaAS08XAAAsH7JtzO0544.png)
評(píng)論