C++ 教程
在Windows平臺(tái),Windows API提供了對多線程的支持。前面進(jìn)程和線程的概念中我們提到,一個(gè)程序至少有一個(gè)線程,這個(gè)線程稱為主線程(main thread),如果我們不顯示地創(chuàng)建線程,那我們產(chǎn)的程序就是只有主線程的間線程程序。
下面,我們看看Windows中線程相關(guān)的操作和方法:
CreateThread 用于創(chuàng)建一個(gè)線程,其函數(shù)原型如下:
HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, //線程安全相關(guān)的屬性,常置為NULL SIZE_T dwStackSize, //新線程的初始化棧在大小,可設(shè)置為0 LPTHREAD_START_ROUTINE lpStartAddress, //被線程執(zhí)行的回調(diào)函數(shù),也稱為線程函數(shù) LPVOID lpParameter, //傳入線程函數(shù)的參數(shù),不需傳遞參數(shù)時(shí)為NULL DWORD dwCreationFlags, //控制線程創(chuàng)建的標(biāo)志 LPDWORD lpThreadId //傳出參數(shù),用于獲得線程ID,如果為NULL則不返回線程ID );
說明:
lpThreadAttributes:指向SECURITY_ATTRIBUTES結(jié)構(gòu)的指針,決定返回的句柄是否可被子進(jìn)程繼承,如果為NULL則表示返回的句柄不能被子進(jìn)程繼承。
lpStartAddress:指向一個(gè)函數(shù)指針,該函數(shù)將被線程調(diào)用執(zhí)行。因此該函數(shù)也被稱為線程函數(shù)(ThreadProc),是線程執(zhí)行的起始地址,線程函數(shù)是一個(gè)回調(diào)函數(shù),由操作系統(tǒng)在線程中調(diào)用。線程函數(shù)的原型如下:
DWORD WINAPI ThreadProc(LPVOID lpParameter); //lpParameter是傳入的參數(shù),是一個(gè)空指針
lpParameter:傳入線程函數(shù)(ThreadProc)的參數(shù),不需傳遞參數(shù)時(shí)為NULL
dwCreationFlags:控制線程創(chuàng)建的標(biāo)志,有三個(gè)類型,0:線程創(chuàng)建后立即執(zhí)行線程;CREATE_SUSPENDED:線程創(chuàng)建后進(jìn)入就緒狀態(tài),直到線程被喚醒時(shí)才調(diào)用;STACK_SIZE_PARAM_IS_A_RESERVATION:dwStackSize 參數(shù)指定線程初始化棧的大小,如果STACK_SIZE_PARAM_IS_A_RESERVATION標(biāo)志未指定,dwStackSize將會(huì)設(shè)為系統(tǒng)預(yù)留的值。
返回值:如果線程創(chuàng)建成功,則返回這個(gè)新線程的句柄,否則返回NULL。如果線程創(chuàng)建失敗,可通過GetLastError函數(shù)獲得錯(cuò)誤信息。
BOOL WINAPI CloseHandle(HANDLE hObject); //關(guān)閉一個(gè)被打開的對象句柄可用這個(gè)函數(shù)關(guān)閉創(chuàng)建的線程句柄,如果函數(shù)執(zhí)行成功則返回true(非0),如果失敗則返回false(0),如果執(zhí)行失敗可調(diào)用GetLastError.函數(shù)獲得錯(cuò)誤信息。
結(jié)果如下:
主線程:i = 0 子線程:i = 0 主線程:i = 1 子線程:i = 1 子線程:i = 2 主線程:i = 2 子線程:i = 3 主線程:i = 3 子線程:i = 4 主線程:i = 4
結(jié)果:
主線程 === 線程1 — 0 0 線程2 — 0 線程1 — 1 主線程 === 1 線程2 — 1 主線程 === 2 線程1 — 2 線程2 — 2 主線程 === 3 線程2 — 3 線程1 — 3 主線程 === 4 線程2 — 4 線程1 — 4 線程2 — 5 請按任意鍵繼續(xù)… 線程2 — 6 線程2 — 7 線程2 — 8 線程2 — 9
從【Demo2】中可以看出,雖然創(chuàng)建的子線程都正常執(zhí)行起來了,但輸出的結(jié)果并不是我們預(yù)期的效果。我們預(yù)期的效果是每輸出一條語句后自動(dòng)換行,但結(jié)果卻并非都是這樣。這是因?yàn)樵诰€程執(zhí)行時(shí)沒有做同步處理,比如第一行的輸出,主線程輸出"主線程 ==="后時(shí)間片已用完,這時(shí)輪到子線程1輸出,在子線程輸出"線程1 —"后時(shí)間片也用完了,這時(shí)又輪到主線程執(zhí)行輸出"0",之后又輪到子線程1輸出"0"。于是就出現(xiàn)了"主線程 === 線程1 — 0 0"的結(jié)果。
主線程:cout << "主線程 === " << i << endl;
子線程:cout << pThreadData->strThreadName << " — " << i << endl;
為避免出現(xiàn)這種情況,我們對線程做一些簡單的同步處理,這里我們用互斥量(Mutex)。
互斥量(Mutex)和二元信號(hào)量類似,資源僅允許一個(gè)線程訪問。與二元信號(hào)量不同的是,信號(hào)量在整個(gè)系統(tǒng)中可以被任意線程獲取和釋放,也就是說,同一個(gè)信號(hào)量可以由一個(gè)線程獲取而由另一線程釋放。而互斥量則要求哪個(gè)線程獲取了該互斥量鎖就由哪個(gè)線程釋放,其它線程越俎代庖釋放互斥量是無效的。
在使用互斥量進(jìn)行線程同步時(shí)會(huì)用到以下幾個(gè)函數(shù):
HANDLE WINAPI CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, //線程安全相關(guān)的屬性,常置為NULL BOOL bInitialOwner, //創(chuàng)建Mutex時(shí)的當(dāng)前線程是否擁有Mutex的所有權(quán) LPCTSTR lpName //Mutex的名稱 );
說明: lpMutexAttributes也是表示安全的結(jié)構(gòu),與CreateThread中的lpThreadAttributes功能相同,表示決定返回的句柄是否可被子進(jìn)程繼承,如果為NULL則表示返回的句柄不能被子進(jìn)程繼承。bInitialOwner表示創(chuàng)建Mutex時(shí)的當(dāng)前線程是否擁有Mutex的所有權(quán),若為TRUE則指定為當(dāng)前的創(chuàng)建線程為Mutex對象的所有者,其它線程訪問需要先ReleaseMutex。lpName為Mutex的名稱。
DWORD WINAPI WaitForSingleObject( HANDLE hHandle, //要獲取的鎖的句柄 DWORD dwMilliseconds //超時(shí)間隔 );
說明: WaitForSingleObject的作用是等待一個(gè)指定的對象(如Mutex對象),直到該對象處于非占用的狀態(tài)(如Mutex對象被釋放)或超出設(shè)定的時(shí)間間隔。除此之外,還有一個(gè)與它類似的函數(shù)WaitForMultipleObjects,它的作用是等待一個(gè)或所有指定的對象,直到所有的對象處于非占用的狀態(tài),或超出設(shè)定的時(shí)間間隔。
hHandle:要等待的指定對象的句柄。dwMilliseconds:超時(shí)的間隔,以毫秒為單位;如果dwMilliseconds為非0,則等待直到dwMilliseconds時(shí)間間隔用完或?qū)ο笞優(yōu)榉钦加玫臓顟B(tài),如果dwMilliseconds 為INFINITE則表示無限等待,直到等待的對象處于非占用的狀態(tài)。
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
說明:釋放所擁有的互斥量鎖對象,hMutex為釋放的互斥量的句柄。
結(jié)果:
主線程 === 0 線程1 — 0 線程2 — 0 主線程 === 1 線程1 — 1 線程2 — 1 主線程 === 2 線程1 — 2 線程2 — 2 主線程 === 3 線程1 — 3 線程2 — 3 主線程 === 4 線程1 — 4 請按任意鍵繼續(xù)… 線程2 — 4 線程2 — 5 線程2 — 6 線程2 — 7 線程2 — 8 線程2 — 9
為進(jìn)一步理解線程同步的重要性和互斥量的使用方法,我們再來看一個(gè)例子。
買火車票是大家春節(jié)回家最為關(guān)注的事情,我們就簡單模擬一下火車票的售票系統(tǒng)(為使程序簡單,我們就抽出最簡單的模型進(jìn)行模擬):有500張從北京到贛州的火車票,在8個(gè)窗口同時(shí)出售,保證系統(tǒng)的穩(wěn)定性和數(shù)據(jù)的原子性。
測試程序:
//售票系統(tǒng) void Test2() { //創(chuàng)建一個(gè)互斥量 g_hMutex = CreateMutex(NULL, FALSE, NULL); //初始化火車票 TICKET ticket; ticket.nCount = 100; strcpy(ticket.strTicketName, "北京-->贛州"); const int THREAD_NUMM = 8; THD_DATA threadSale[THREAD_NUMM]; HANDLE hThread[THREAD_NUMM]; for(int i = 0; i < THREAD_NUMM; ++ i) { threadSale[i].pTicket = &ticket; string strThreadName = convertToString(i); strThreadName = "窗口" + strThreadName; strcpy(threadSale[i].strThreadName, strThreadName.c_str()); //創(chuàng)建線程 hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL); //請求獲得一個(gè)互斥量鎖 WaitForSingleObject(g_hMutex, INFINITE); cout << threadSale[i].strThreadName << "開始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl; //釋放互斥量鎖 ReleaseMutex(g_hMutex); //關(guān)閉線程 CloseHandle(hThread[i]); } system("pause"); }
結(jié)果:
窗口0開始出售 北京–>贛州 的票… 窗口0出售第100的票,出票成功!剩余99張票. 窗口1開始出售 北京–>贛州 的票… 窗口1出售第99的票,出票成功!剩余98張票. 窗口0出售第98的票,出票成功!剩余97張票. 窗口2開始出售 北京–>贛州 的票… 窗口2出售第97的票,出票成功!剩余96張票. 窗口1出售第96的票,出票成功!剩余95張票. 窗口0出售第95的票,出票成功!剩余94張票. 窗口3開始出售 北京–>贛州 的票… 窗口3出售第94的票,出票成功!剩余93張票. 窗口2出售第93的票,出票成功!剩余92張票. 窗口1出售第92的票,出票成功!剩余91張票. 窗口0出售第91的票,出票成功!剩余90張票. 窗口4開始出售 北京–>贛州 的票… 窗口4出售第90的票,出票成功!剩余89張票. 窗口3出售第89的票,出票成功!剩余88張票. 窗口2出售第88的票,出票成功!剩余87張票. 窗口1出售第87的票,出票成功!剩余86張票. 窗口0出售第86的票,出票成功!剩余85張票. 窗口5開始出售 北京–>贛州 的票… 窗口5出售第85的票,出票成功!剩余84張票. 窗口4出售第84的票,出票成功!剩余83張票. 窗口3出售第83的票,出票成功!剩余82張票. 窗口2出售第82的票,出票成功!剩余81張票.
來源:http://blog.csdn.net/luoweifu/article/details/46835437