在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

了解多線程并深入分析CreateThread與_beginthreadex本質區別

C語言專家集中營 ? 2018-01-09 17:08 ? 次閱讀

本文將帶領你與多線程作第一次親密接觸,并深入分析CreateThread與_beginthreadex的本質區別,相信閱讀本文后你能輕松的使用多線程并能流暢準確的回答CreateThread與_beginthreadex到底有什么區別,在實際的編程中到底應該使用CreateThread還是_beginthreadex?

使用多線程其實是非常容易的,下面這個程序的主線程會創建了一個子線程并等待其運行完畢,子線程就輸出它的線程ID號然后輸出一句經典名言——Hello World。整個程序的代碼非常簡短,只有區區幾行。

[cpp]view plaincopy

//最簡單的創建多線程實例

#include

#include

//子線程函數

DWORDWINAPIThreadFun(LPVOIDpM)

{

printf("子線程的線程ID號為:%d\n子線程輸出HelloWorld\n",GetCurrentThreadId());

return0;

}

//主函數,所謂主函數其實就是主線程執行的函數。

intmain()

{

printf("最簡單的創建多線程實例\n");

printf("--byMoreWindows(http://blog.csdn.net/MoreWindows)--\n\n");

HANDLEhandle=CreateThread(NULL,0,ThreadFun,NULL,0,NULL);

WaitForSingleObject(handle,INFINITE);

return0;

}

運行結果如下所示:

了解多線程并深入分析CreateThread與_beginthreadex本質區別

下面來細講下代碼中的一些函數

第一個CreateThread

函數功能:創建線程

函數原型:

HANDLEWINAPICreateThread(

LPSECURITY_ATTRIBUTESlpThreadAttributes,

SIZE_TdwStackSize,

LPTHREAD_START_ROUTINElpStartAddress,

LPVOIDlpParameter,

DWORDdwCreationFlags,

LPDWORDlpThreadId

);

函數說明:

第一個參數表示線程內核對象的安全屬性,一般傳入NULL表示使用默認設置。

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

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

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

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

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

函數返回值:

成功返回新線程的句柄,失敗返回NULL。

第二個WaitForSingleObject

函數功能:等待函數–使線程進入等待狀態,直到指定的內核對象被觸發。

函數原形:

DWORDWINAPIWaitForSingleObject(

HANDLEhHandle,

DWORDdwMilliseconds

);

函數說明:

第一個參數為要等待的內核對象。

第二個參數為最長等待的時間,以毫秒為單位,如傳入5000就表示5秒,傳入0就立即返回,傳入INFINITE表示無限等待。

因為線程的句柄在線程運行時是未觸發的,線程結束運行,句柄處于觸發狀態。所以可以用WaitForSingleObject()來等待一個線程結束運行。

函數返回值:

在指定的時間內對象被觸發,函數返回WAIT_OBJECT_0。超過最長等待時間對象仍未被觸發返回WAIT_TIMEOUT。傳入參數有錯誤將返回WAIT_FAILED

CreateThread()函數是Windows提供的API接口,在C/C++語言另有一個創建線程的函數_beginthreadex(),在很多書上(包括《Windows核心編程》)提到過盡量使用_beginthreadex()來代替使用CreateThread(),這是為什么了?下面就來探索與發現它們的區別吧。

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

[cpp]view plaincopy

if(system("notepad.exereadme.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()函數的源代碼(我在這份代碼中增加了一些注釋)以便讀者更好的理解_beginthreadex()函數與CreateThread()函數的區別。

[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見注1

uintptr_tthdl;//threadhandle線程句柄

unsignedlongerr=0L;//ReturnfromGetLastError()

unsigneddummyid;//dummyreturnedthreadID線程ID號

//validationsection檢查initialcode是否為NULL

_VALIDATE_RETURN(initialcode!=NULL,EINVAL,0);

//InitializeFlsGetValuefunctionpointer

__set_flsgetvalue();

//Allocateandinitializeaper-threaddatastructurefortheto-be-createdthread.

//相當于new一個_tiddata結構,并賦給_ptiddata指針。

if((ptd=(_ptiddata)_calloc_crt(1,sizeof(struct_tiddata)))==NULL)

gotoerror_return;

//Initializetheper-threaddata

//初始化線程的_tiddata塊即CRT數據區域見注2

_initptd(ptd,_getptd()->ptlocinfo);

//設置_tiddata結構中的其它數據,這樣這塊_tiddata塊就與線程聯系在一起了。

ptd->_initaddr=(void*)initialcode;//線程函數地址

ptd->_initarg=argument;//傳入的線程參數

ptd->_thandle=(uintptr_t)(-1);

#ifdefined(_M_CEE)||defined(MRTDLL)

if(!_getdomain(&(ptd->__initDomain)))//見注3

{

gotoerror_return;

}

#endif//defined(_M_CEE)||defined(MRTDLL)

//Makesurenon-NULLthrdaddrispassedtoCreateThread

if(thrdaddr==NULL)//判斷是否需要返回線程ID號

thrdaddr=&dummyid;

//Createthenewthreadusingtheparameterssuppliedbythecaller.

//_beginthreadex()最終還是會調用CreateThread()來向系統申請創建線程

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);//線程創建成功,返回新線程的句柄.

//Errorreturn

error_return:

//EitherptdisNULL,oritpointstotheno-longer-necessaryblock

//calloc-edforthe_tiddatastructwhichshouldnowbefreedup.

//回收由_calloc_crt()申請的_tiddata塊

_free_crt(ptd);

//Maptheerror,ifnecessary.

//Note:thisroutinereturns0forfailure,justliketheWin32

//APICreateThread,but_beginthread()returns-1forfailure.

//校正錯誤代號(可以調用GetLastError()得到錯誤代號)

if(err!=0L)

_dosmaperr(err);

return((uintptr_t)0);//返回值為NULL的效句柄

}

講解下部分代碼:

注1._ptiddataptd;中的_ptiddata是個結構體指針。在mtdll.h文件被定義:

typedefstruct_tiddata*_ptiddata

微軟對它的注釋為Structure for each thread's data。這是一個非常大的結構體,有很多成員。本文由于篇幅所限就不列出來了。

注2._initptd(ptd,_getptd()->ptlocinfo);微軟對這一句代碼中的getptd()的說明為:

/* return address of per-thread CRT data */

_ptiddata__cdecl_getptd(void);

對_initptd()說明如下:

/* initialize a per-thread CRT data block */

void__cdecl_initptd(_Inout_ _ptiddata_Ptd,_In_opt_ pthreadlocinfo_Locale);

注釋中的CRT(C Runtime Library)即標準C運行庫。

注3.if(!_getdomain(&(ptd->__initDomain)))中的_getdomain()函數代碼可以在thread.c文件中找到,其主要功能是初始化COM環境。

由上面的源代碼可知,_beginthreadex()函數在創建新線程時會分配并初始化一個_tiddata塊。這個_tiddata塊自然是用來存放一些需要線程獨享的數據。事實上新線程運行時會首先將_tiddata塊與自己進一步關聯起來。然后新線程調用標準C運行庫函數如strtok()時就會先取得_tiddata塊的地址再將需要保護的數據存入_tiddata塊中。這樣每個線程就只會訪問和修改自己的數據而不會去篡改其它線程的數據了。因此,如果在代碼中有使用標準C運行庫中的函數時,盡量使用_beginthreadex()來代替CreateThread()。相信閱讀到這里時,你會對這句簡短的話有個非常深刻的印象,如果有面試官問起,你也可以流暢準確的回答了^_^。

接下來,類似于上面的程序用CreateThread()創建輸出“Hello World”的子線程,下面使用_beginthreadex()來創建多個子線程:

[cpp]view plaincopy

//創建多子個線程實例

#include

#include

#include

//子線程函數

unsignedint__stdcallThreadFun(PVOIDpM)

{

printf("線程ID號為%4d的子線程說:HelloWorld\n",GetCurrentThreadId());

return0;

}

//主函數,所謂主函數其實就是主線程執行的函數。

intmain()

{

printf("創建多個子線程實例\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;

}

運行結果如下:

了解多線程并深入分析CreateThread與_beginthreadex本質區別

圖中每個子線程說的都是同一句話,不太好看。能不能來一個線程報數功能,即第一個子線程輸出1,第二個子線程輸出2,第三個子線程輸出3,……。要實現這個功能似乎非常簡單——每個子線程對一個全局變量進行遞增并輸出就可以了。代碼如下:

[cpp]view plaincopy

//子線程報數

#include

#include

#include

intg_nCount;

//子線程函數

unsignedint__stdcallThreadFun(PVOIDpM)

{

g_nCount++;

printf("線程ID號為%4d的子線程報數%d\n",GetCurrentThreadId(),g_nCount);

return0;

}

//主函數,所謂主函數其實就是主線程執行的函數。

intmain()

{

printf("子線程報數\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;

}

對一次運行結果截圖如下:

了解多線程并深入分析CreateThread與_beginthreadex本質區別

顯示結果從1數到10,看起來好象沒有問題。

答案是不對的,雖然這種做法在邏輯上是正確的,但在多線程環境下這樣做是會產生嚴重的問題。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • C語言
    +關注

    關注

    180

    文章

    7615

    瀏覽量

    137827
  • 多線程
    +關注

    關注

    0

    文章

    278

    瀏覽量

    20076

原文標題:多線程CreateThread與_beginthreadex本質區別

文章出處:【微信號:C_Expert,微信公眾號:C語言專家集中營】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    FPGA與ARM的本質區別是什么?

    FPGA(Field-Programmable Gate Array)與ARM在多個方面存在本質區別。 首先,從它們的定義和結構上來看,FPGA是一種現場可編程門陣列,屬于可編程器件的一種。它的內部
    發表于 04-28 08:56

    FPGA與ARM的本質區別

    FPGA(Field-Programmable Gate Array)與ARM在多個方面存在本質區別。 首先,從它們的定義和結構上來看,FPGA是一種現場可編程門陣列,屬于可編程器件的一種。它的內部
    發表于 04-28 09:00

    uCOS任務堆棧的深入分析(轉)

    uCOS任務堆棧的深入分析(轉)
    發表于 08-24 23:30

    PLC與單片機的本質區別

    PLC與單片機的本質區別是什么?
    發表于 01-13 07:55

    多線程和多進程的區別

    6.你的數據庫一會又500個連接數,一會有10個,你分析一下情況7.udp和tcp的區別8.多線程和多進程的區別9.有一臺web服務器,你選擇用多線
    發表于 07-19 07:21

    請問PLC與單片機的本質區別在哪里?

    PLC與單片機的本質區別在哪里?
    發表于 11-09 06:04

    筆記本的結構深入分析

    筆記本的結構深入分析  電腦技術的應用為我們的生活和工作帶來了巨大改變,使我們的生活學習工作有了質的轉變。普通的用戶對電腦的了解
    發表于 01-21 15:53 ?4318次閱讀

    基于SWT的多線程解決方案

    介紹了在基于 SWT 的C / S 結構的項目開發中,當用UI 主線程進行后臺數據讀取或交換時導致的UI 線程堵塞現象的解決方案。通過對UI 線程深入了解,利用
    發表于 06-07 17:08 ?0次下載

    多核與多線程技術的區別

    毫無疑問的,多核、多線程此二詞已快成為當今處理器架構設計中的兩大顯學,如同歷史戰國時代以儒、墨兩大派的顯學,只不過當年兩大治世思想學派是爭得你死我亡,而多核、多線程則是相互兼容蓄,今日幾乎任何
    發表于 10-19 16:26 ?0次下載

    多線程好還是單線程好?單線程多線程區別 優缺點分析

    摘要:如今單線程多線程已經得到普遍運用,那么到底多線程好還是單線程好呢?單線程多線程
    發表于 12-08 09:33 ?8.2w次閱讀

    光纖和光纜的本質區別是什么

    相信大家都聽過光纖盒光纜,那光纖和光纜一樣嗎?本質區別在哪里?科蘭綜合布線小編指出:其實兩者都是一種傳輸介質。但嚴格意義上講,兩者是不相同的產品,下面一起來了解一下兩者區別
    發表于 03-23 10:24 ?6363次閱讀

    分析unidbg(unidbgMutil)多線程機制

    由于在工作中遇到了某翻譯so中有多線程調用,因此使用unidbg分析(基于unidbgMutilThread)增加阻塞喚醒機制(futex系統調用),但仍未調用成功
    的頭像 發表于 05-20 17:23 ?3058次閱讀
    <b class='flag-5'>分析</b>unidbg(unidbgMutil)<b class='flag-5'>多線程</b>機制

    線程是什么的基本單位 進程與線程本質區別

    的代碼、數據以及用于執行這些代碼的上下文信息。一個進程可以由一個或多個線程組成,從而并發執行多個任務。 本質區別: 資源擁有方式:進程是資源分配的基本單位,每個進程擁有獨立的內存空間、文件描述符、頁面表等資源,之
    的頭像 發表于 02-02 16:30 ?1023次閱讀

    聚徽觸控-工控機和商用電腦本質區別是什么

    工控機和商用電腦在多個方面存在本質區別,具體如下:
    的頭像 發表于 07-16 09:19 ?360次閱讀

    Python中多線程和多進程的區別

    Python作為一種高級編程語言,提供了多種并發編程的方式,其中多線程與多進程是最常見的兩種方式之一。在本文中,我們將探討Python中多線程與多進程的概念、區別以及如何使用線程池與進
    的頭像 發表于 10-23 11:48 ?500次閱讀
    Python中<b class='flag-5'>多線程</b>和多進程的<b class='flag-5'>區別</b>
    主站蜘蛛池模板: 久久这里只精品热在线8 | 午夜激情啪啪 | 天天干夜夜曰 | 在线看av网址 | 日本黄色一级网站 | 欧美最猛黑人xxxx黑人猛交69 | 天天色狠狠干 | 五月综合在线 | 婷婷视频网 | 日本高清中文字幕在线观穿线视频 | 国产精品国产主播在线观看 | 日韩一级欧美一级在线观看 | 亚洲播放 | 色综合视频在线 | 天天操天天干天天舔 | 天堂视频在线观看 | 五月婷婷深深爱 | 好黄好猛好爽好痛的视频 | 天天搞夜夜操 | 一级 黄 色 毛片 | 国产精品成人观看视频国产奇米 | 亚洲天堂亚洲天堂 | 末发育娇小性色xxxxx视频 | 午夜久久久精品 | 欧美色婷婷 | 222网站高清免费观看 | 22sihu国产精品视频影视资讯 | 日日噜噜夜夜狠狠久久丁香 | 婷婷丁香五 | 免费看片aⅴ免费大片 | 亚洲天堂网在线观看 | 天堂在线视频精品 | 三级在线看 | 在线91精品亚洲网站精品成人 | 欧美成人高清性色生活 | 视频一区二区在线 | 国产女在线| 亚洲电影av | 很黄很色的网站 | 狠狠干狠狠鲁 | 亚洲一级特黄特黄的大片 |