秒殺多線程
請耐心讀完,并自己敲一下給的實例代碼,相信只要一步步來還是很容易搞懂多線程的。
CreateThread與_beginthreadex
1. 一個簡單的主線程創(chuàng)建子線程

2. 代碼函數說明
2.1?CreateThread()
Windows文檔CreateThread()函數說明:https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread
函數功能:創(chuàng)建函數
函數原型:
參數說明:
第一個參數表示線程內核對象的安全屬性,一般傳入NULL表示使用默認設置。
第二個參數表示線程棧空間大小。傳入0表示使用默認大小(1MB)。
第三個參數表示新線程所執(zhí)行的線程函數地址,多個線程可以使用同一個函數地址。
第四個參數是傳給線程函數的參數。
第五個參數指定額外的標志來控制線程的創(chuàng)建,為0表示線程創(chuàng)建之后立即就可以進行調度,如果為CREATE_SUSPENDED則表示線程在掛起狀態(tài)下創(chuàng)建,線程創(chuàng)建后暫停運行,這樣它就無法調度,直到調用ResumeThread()。
第六個參數將返回線程的ID號,傳入NULL表示不需要返回該線程ID號。
函數返回值:
成功返回新線程的句柄,失敗返回NULL。
2.2 WaitForSingleObject()
Windows文檔WaitForSingleObject()函數說明:https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
函數功能:等待函數 – 使線程進入等待狀態(tài),直到指定的內核對象被觸發(fā)。
函數原型:
參數說明:
第一個參數為要等待的內核對象。
第二個參數為最長等待的時間,以毫秒為單位,如傳入5000就表示5秒,傳入0就立即返回,傳入INFINITE表示無限等待。
因為線程的句柄在線程運行時是未觸發(fā)的,線程結束運行,句柄處于觸發(fā)狀態(tài)。所以可以用WaitForSingleObject()來等待一個線程結束運行。
函數返回值:
在指定的時間內對象被觸發(fā),函數返回WAIT_OBJECT_0。超過最長等待時間對象仍未被觸發(fā)返回WAIT_TIMEOUT。傳入參數有錯誤將返回WAIT_FAILED
_beginthreadex()
CreateThread()函數是Windows提供的API接口,在C/C++語言另有一個創(chuàng)建線程的函數_beginthreadex(),在很多書上(包括《Windows核心編程》)提到過盡量使用_beginthreadex()來代替使用CreateThread(),這是為什么了?下面就來探索與發(fā)現(xiàn)它們的區(qū)別吧。
首先要從標準C運行庫與多線程的矛盾說起,標準C運行庫在1970年被實現(xiàn)了,由于當時沒任何一個操作系統(tǒng)提供對多線程的支持。因此編寫標準C運行庫的程序員根本沒考慮多線程程序使用標準C運行庫的情況。比如標準C運行庫的全局變量errno。很多運行庫中的函數在出錯時會將錯誤代號賦值給這個全局變量,這樣可以方便調試。但如果有這樣的一個代碼片段:
假設某個線程A在執(zhí)行上面的代碼,該線程在調用system()之后且尚未調用switch()語句時另外一個線程B啟動了,這個線程B也調用了標準C運行庫的函數,不幸的是這個函數執(zhí)行出錯了并將錯誤代號寫入全局變量errno中。這樣線程A一旦開始執(zhí)行switch()語句時,它將訪問一個被B線程改動了的errno。這種情況必須要加以避免!因為不單單是這一個變量會出問題,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函數也會遇到這種由多個線程訪問修改導致的數據覆蓋問題。
為了解決這個問題,Windows操作系統(tǒng)提供了這樣的一種解決方案——每個線程都將擁有自己專用的一塊內存區(qū)域來供標準C運行庫中所有有需要的函數使用。而且這塊內存區(qū)域的創(chuàng)建就是由C/C++運行庫函數_beginthreadex()來負責的。
區(qū)別。
源代碼:
講解下部分代碼:
注1._ptiddataptd;中的_ptiddata是個結構體指針。在mtdll.h文件被定義:typedefstruct_tiddata * _ptiddata
微軟對它的注釋為Structure for each thread's data。這是一個非常大的結構體,有很多成員。
注2._initptd(ptd, _getptd()->ptlocinfo);微軟對這一句代碼中的getptd()的說明為:
對_initptd()說明如下:
注3.if(!_getdomain(&(ptd->__initDomain)))
中的_getdomain()
函數代碼可以在thread.c文件中找到,其主要功能是初始化COM環(huán)境。
由上面的源代碼可知,beginthreadex()函數在創(chuàng)建新線程時會分配并初始化一個tiddata塊。這個tiddata塊自然是用來存放一些需要線程獨享的數據。事實上新線程運行時會首先將tiddata塊與自己進一步關聯(lián)起來。然后新線程調用標準C運行庫函數如strtok()時就會先取得tiddata塊的地址再將需要保護的數據存入tiddata塊中。這樣每個線程就只會訪問和修改自己的數據而不會去篡改其它線程的數據了。因此,如果在代碼中有使用標準C運行庫中的函數時,盡量使用_beginthreadex()來代替CreateThread()。

實現(xiàn)一個線程報數的功能

顯示結果從1數到10,看起來好象沒有問題。答案是不對的,雖然這種做法在邏輯上是正確的,但在多線程環(huán)境下這樣做是會產生嚴重的問題。
下一篇將介紹原子操作,并說明上述做法產生的問題
參考:https://blog.csdn.net/morewindows/article/details/7421759