C++實(shí)現(xiàn)線(xiàn)程同步的四種方式總結(jié)
內(nèi)核態(tài)
互斥變量
互斥對(duì)象包含一個(gè)使用數(shù)量,一個(gè)線(xiàn)程ID和一個(gè)計(jì)數(shù)器。其中線(xiàn)程ID用于標(biāo)識(shí)系統(tǒng)中的哪個(gè)線(xiàn)程當(dāng)前擁有互斥對(duì)象,計(jì)數(shù)器用于指明該線(xiàn)程擁有互斥對(duì)象的次數(shù)。
創(chuàng)建互斥對(duì)象:調(diào)用函數(shù)CreateMutex。調(diào)用成功,該函數(shù)返回所創(chuàng)建的互斥對(duì)象的句柄。
請(qǐng)求互斥對(duì)象所有權(quán):調(diào)用函數(shù)WaitForSingleObject函數(shù)。線(xiàn)程必須主動(dòng)請(qǐng)求共享對(duì)象的所有權(quán)才能獲得所有權(quán)。
釋放指定互斥對(duì)象的所有權(quán):調(diào)用ReleaseMutex函數(shù)。線(xiàn)程訪(fǎng)問(wèn)共享資源結(jié)束后,線(xiàn)程要主動(dòng)釋放對(duì)互斥對(duì)象的所有權(quán),使該對(duì)象處于已通知狀態(tài)。
創(chuàng)建互斥對(duì)象函數(shù)
HANDLE WINAPI CreateMutexW( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向安全屬性 _In_ BOOL bInitialOwner, //初始化互斥對(duì)象的所有者 TRUE 立即擁有互斥體 _In_opt_ LPCWSTR lpName //指向互斥對(duì)象名的指針 L“Bingo” );
- 第一個(gè)參數(shù)表示安全屬性,這是每一個(gè)創(chuàng)建內(nèi)核對(duì)象都會(huì)有的參數(shù),NULL表示默認(rèn)安全屬性
- 第二個(gè)參數(shù)表示互斥對(duì)象所有者,TRUE立即擁有互斥體
- 第三個(gè)參數(shù)表示指向互斥對(duì)象的指針
代碼示例
下面這段程序聲明了一個(gè)全局整型變量,并初始化為0。一個(gè)線(xiàn)程函數(shù)對(duì)這個(gè)變量進(jìn)行+1操作,執(zhí)行50000次;另一個(gè)線(xiàn)程函數(shù)對(duì)這個(gè)變量-1操作,執(zhí)行50000次。兩個(gè)線(xiàn)程函數(shù)各創(chuàng)建25個(gè)。因?yàn)槲覀兪褂昧嘶コ庾兞浚?0個(gè)線(xiàn)程會(huì)按照一定順序?qū)@變量操作,因此最后結(jié)果為0。
#include <stdio.h> #include <windows.h> #include <process.h> #define NUM_THREAD 50 unsigned WINAPI threadInc(void* arg); unsigned WINAPI threadDes(void* arg); long long num = 0; HANDLE hMutex; int main() { //內(nèi)核對(duì)象數(shù)組 HANDLE tHandles[NUM_THREAD]; int i; //創(chuàng)建互斥信號(hào)量 hMutex = CreateMutex(0, FALSE, NULL); printf("sizeof long long: %d \n", sizeof(long long)); for (i = 0; i < NUM_THREAD; i++) { if (i % 2) tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL); else tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL); } WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE); //關(guān)閉互斥對(duì)象 CloseHandle(hMutex); printf("result: %lld \n", num); return 0; } unsigned WINAPI threadInc(void* arg){ int i; //請(qǐng)求使用 WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < 500000; i++) num += 1; //釋放 ReleaseMutex(hMutex); return 0; } unsigned WINAPI threadDes(void* arg){ int i; //請(qǐng)求 WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < 500000; i++) num -= 1; //釋放 ReleaseMutex(hMutex); return 0; }
事件對(duì)象
事件對(duì)象也屬于內(nèi)核對(duì)象,它包含以下三個(gè)成員:
- 使用計(jì)數(shù);
- 用于指明該事件是一個(gè)自動(dòng)重置的事件還是一個(gè)人工重置的事件的布爾值;
- 用于指明該事件處于已通知狀態(tài)還是未通知狀態(tài)的布爾值。
事件對(duì)象有兩種類(lèi)型:人工重置的事件對(duì)象和自動(dòng)重置的事件對(duì)象。這兩種事件對(duì)象的區(qū)別在于當(dāng)人工重置的事件對(duì)象得到通知時(shí),等待該事件對(duì)象的所有線(xiàn)程均變?yōu)榭烧{(diào)度線(xiàn)程;而當(dāng)一個(gè)自動(dòng)重置的事件對(duì)象得到通知時(shí),等待該事件對(duì)象的線(xiàn)程中只有一個(gè)線(xiàn)程變?yōu)榭烧{(diào)度線(xiàn)程。
1.創(chuàng)建事件對(duì)象
調(diào)用CreateEvent函數(shù)創(chuàng)建或打開(kāi)一個(gè)命名的或匿名的事件對(duì)象。
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全屬性 BOOL bManualReset, // 復(fù)位方式 TRUE 必須用ResetEvent手動(dòng)復(fù)原 FALSE 自動(dòng)還原為無(wú)信號(hào)狀態(tài) BOOL bInitialState, // 初始狀態(tài) TRUE 初始狀態(tài)為有信號(hào)狀態(tài) FALSE 無(wú)信號(hào)狀態(tài) LPCTSTR lpName //對(duì)象名稱(chēng) NULL 無(wú)名的事件對(duì)象 );
- 第一個(gè)參數(shù)表示安全屬性,這是創(chuàng)建內(nèi)核對(duì)象函數(shù)都有的一個(gè)參數(shù),NULL表示默認(rèn)安全屬性
- 第二個(gè)參數(shù)表示復(fù)位方式,如果是TRUE,則必須手動(dòng)調(diào)用ResetEvent函數(shù)復(fù)位,F(xiàn)ALSE則表示自動(dòng)還原
- 第三個(gè)參數(shù)表示初始狀態(tài),TRUE表示初始為有信號(hào)狀態(tài),F(xiàn)ALSE為無(wú)信號(hào)
- 第四個(gè)參數(shù)表示對(duì)象名稱(chēng),NULL表示無(wú)名的事件對(duì)象
2. 設(shè)置事件對(duì)象狀態(tài)
調(diào)用SetEvent函數(shù)把指定的事件對(duì)象設(shè)置為有信號(hào)狀態(tài)。
3. 重置事件對(duì)象狀態(tài)
調(diào)用ResetEvent函數(shù)把指定的事件對(duì)象設(shè)置為無(wú)信號(hào)狀態(tài)。
4. 請(qǐng)求事件對(duì)象
線(xiàn)程通過(guò)調(diào)用WaitForSingleObject函數(shù)請(qǐng)求事件對(duì)象。
代碼示例
下面這段程序是一段火車(chē)售票:線(xiàn)程A和B會(huì)不停的購(gòu)票直到票數(shù)小于0,執(zhí)行完畢。在判斷票數(shù)前會(huì)先申請(qǐng)事件對(duì)象,購(gòu)票結(jié)束或者票數(shù)小于0時(shí)則會(huì)釋放事件對(duì)象(事件對(duì)象置位有信號(hào))。因?yàn)槲覀兪褂昧耸录?duì)象。兩個(gè)線(xiàn)程會(huì)按某一順序購(gòu)票,直到票數(shù)小于0。
#include<iostream> #include<Windows.h> #include<process.h> using namespace std; //火車(chē)站賣(mài)票 int iTickets = 100;//總票數(shù) HANDLE g_hEvent; unsigned WINAPI SellTicketA(void* lpParam) { while (true) { WaitForSingleObject(g_hEvent, INFINITE); if (iTickets > 0) { Sleep(1); printf("A買(mǎi)了一張票,剩余%d\n", iTickets--); } else { SetEvent(g_hEvent); break; } SetEvent(g_hEvent); } return 0; } unsigned WINAPI SellTicketB(void* lpParam) { while (true) { WaitForSingleObject(g_hEvent, INFINITE); if (iTickets > 0) { Sleep(1); printf("B買(mǎi)了一張票,剩余%d\n", iTickets--); } else { SetEvent(g_hEvent); break; } SetEvent(g_hEvent); } return 0; } int main() { HANDLE hThreadA, hThreadB; hThreadA = (HANDLE)_beginthreadex(NULL, 0, SellTicketA, NULL, 0, NULL); hThreadB = (HANDLE)_beginthreadex(NULL, 0, SellTicketB, NULL, 0, NULL); CloseHandle(hThreadA); CloseHandle(hThreadB); g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); SetEvent(g_hEvent); Sleep(4000); CloseHandle(g_hEvent); system("pause"); return 0; }
資源信號(hào)量
信號(hào)量(semaphore)是操作系統(tǒng)用來(lái)解決并發(fā)中的互斥和同步問(wèn)題的一種方法。與互斥量不同的地方是,它允許多個(gè)線(xiàn)程在同一時(shí)刻訪(fǎng)問(wèn)同一資源,但是需要限制在同一時(shí)刻訪(fǎng)問(wèn)此資源的最大線(xiàn)程數(shù)目。
創(chuàng)建信號(hào)量函數(shù)
HANDLE WINAPI CreateSemaphoreW( _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Null 安全屬性 _In_ LONG lInitialCount, //初始化時(shí),共有多少個(gè)資源是可以用的。 0:未觸發(fā)狀//態(tài)(無(wú)信號(hào)狀態(tài)),表示沒(méi)有可用資源 _In_ LONG lMaximumCount, //能夠處理的最大的資源數(shù)量 3 _In_opt_ LPCWSTR lpName //NULL 信號(hào)量的名稱(chēng) );
- 第一個(gè)參數(shù)表示安全屬性,這是創(chuàng)建內(nèi)核對(duì)象函數(shù)都會(huì)有的參數(shù),NULL表示默認(rèn)安全屬性
- 第二個(gè)參數(shù)表示初始時(shí)有多少個(gè)資源可用,0表示無(wú)任何資源(未觸發(fā)狀態(tài))
- 第三個(gè)參數(shù)表示最大資源數(shù)
- 第四個(gè)參數(shù)表示信號(hào)量的名稱(chēng),NULL表示無(wú)名稱(chēng)的信號(hào)量對(duì)象
增加/釋放信號(hào)量
ReleaseSemaphore( _In_ HANDLE hSemaphore, //信號(hào)量的句柄 _In_ LONG lReleaseCount, //將lReleaseCount值加到信號(hào)量的當(dāng)前資源計(jì)數(shù)上面 0-> 1 _Out_opt_ LPLONG lpPreviousCount //當(dāng)前資源計(jì)數(shù)的原始值 );
- 第一個(gè)參數(shù)表示信號(hào)量句柄,也就是調(diào)用創(chuàng)建信號(hào)量函數(shù)時(shí)返回的句柄
- 第二個(gè)參數(shù)表示釋放的信號(hào)量個(gè)數(shù),該值必須大于0,但不能大于信號(hào)量的最大計(jì)數(shù)
- 第三個(gè)參數(shù)表示指向要接收信號(hào)量的上一個(gè)計(jì)數(shù)的變量的指針。如果不需要上一個(gè)計(jì)數(shù), 則此參數(shù)可以為NULL 。
關(guān)閉句柄
CloseHandle( _In_ _Post_ptr_invalid_ HANDLE hObject );
代碼示例
下面這段程序創(chuàng)建了兩個(gè)信號(hào)資源,其最大資源都為1;一個(gè)初始資源為0,另一個(gè)初始資源為1。線(xiàn)程中的for循環(huán)每執(zhí)行一次會(huì)將另一個(gè)要申請(qǐng)的信號(hào)資源的可用資源數(shù)+1。因此程序的執(zhí)行結(jié)果為兩個(gè)線(xiàn)程中的for循環(huán)交替執(zhí)行。
#include<iostream> #include<Windows.h> #include<process.h> using namespace std; static HANDLE semOne; static HANDLE semTwo; static int num; /* * 信號(hào)資源semOne初始為0,最大1個(gè)資源可用 * 信號(hào)資源semTwo初始為1,最大1個(gè)資源可用 */ unsigned WINAPI Read(void* arg) { int i; for (i = 0; i < 5; i++) { fputs("Input num:\n", stdout); printf("begin read\n"); WaitForSingleObject(semTwo, INFINITE); printf("beginning read\n"); scanf("%d", &num); ReleaseSemaphore(semOne, 1, NULL); } return 0; } unsigned WINAPI Accu(void* arg) { int sum = 0, i; for (i = 0; i < 5; ++i) { printf("begin Accu\n"); WaitForSingleObject(semOne, INFINITE); printf("beginning Accu\n"); sum += num; printf("sum=%d\n", sum); ReleaseSemaphore(semTwo, 1, NULL); } return 0; } int main() { HANDLE hThread1, hThread2; semOne = CreateSemaphore(NULL, 0, 1, NULL);//初始值沒(méi)有可用資源 semTwo = CreateSemaphore(NULL, 1, 1, NULL);//初始值有一個(gè)可用資源 hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL); hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL); WaitForSingleObject(hThread1, INFINITE); WaitForSingleObject(hThread2, INFINITE); CloseHandle(semOne); CloseHandle(semTwo); system("pause"); return 0; }
用戶(hù)態(tài)
關(guān)鍵代碼
關(guān)鍵代碼段,也稱(chēng)為臨界區(qū),工作在用戶(hù)方式下。它是指一個(gè)小代碼段,在代碼能夠執(zhí)行前,它必須獨(dú)占對(duì)某些資源的訪(fǎng)問(wèn)權(quán)。通常把多線(xiàn)程中訪(fǎng)問(wèn)同一種資源的那部分代碼當(dāng)做關(guān)鍵代碼段。
1.初始化關(guān)鍵代碼段
調(diào)用InitializeCriticalSection函數(shù)初始化一個(gè)關(guān)鍵代碼段
InitialzieCriticalSection( _Out_ LPRRITICAL_SECTION lpCriticalSection );
該函數(shù)只有一個(gè)指向CRITICAL_SECTION結(jié)構(gòu)體的指針。在調(diào)用InitializeCriticalSection函數(shù)之前,首先需要構(gòu)造一個(gè)CRITICAL_SCTION結(jié)構(gòu)體類(lèi)型的對(duì)象,然后將該對(duì)象的地址傳遞給InitializeCriticalSection函數(shù)。
2進(jìn)入關(guān)鍵代碼
VOID WINAPI EnterCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
調(diào)用EnterCriticalSection函數(shù),以獲得指定的臨界區(qū)對(duì)象的所有權(quán),該函數(shù)等待指定的臨界區(qū)對(duì)象的所有權(quán),如果該所有權(quán)賦予了調(diào)用線(xiàn)程,則該函數(shù)就返回;否則該函數(shù)會(huì)一直等待,從而導(dǎo)致線(xiàn)程等待。
3.退出關(guān)鍵代碼段
VOID WINAPI LeaveCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
線(xiàn)程使用完臨界區(qū)所保護(hù)的資源之后,需要調(diào)用LeaveCriticalSection函數(shù),釋放指定的臨界區(qū)對(duì)象的所有權(quán)。之后,其他想要獲得該臨界區(qū)對(duì)象所有權(quán)的線(xiàn)程就可以獲得該所有權(quán),從而進(jìn)入關(guān)鍵代碼段,訪(fǎng)問(wèn)保護(hù)的資源。
4.刪除臨界區(qū)
WINBASEAPI VOID WINAPI DeleteCriticalSection( _Inout_ LPCRITICAL_SECTION lpCriticalSection );
當(dāng)臨界區(qū)不再需要時(shí),可以調(diào)用DeleteCriticalSection函數(shù)釋放該對(duì)象,該函數(shù)將釋放一個(gè)沒(méi)有被任何線(xiàn)程所擁有的臨界區(qū)對(duì)象的所有資源。
程序?qū)嵗?/p>
下面這段程序同樣也是火車(chē)售票,其工作邏輯與上面的事件對(duì)象基本吻合。
#include<iostream> #include<Windows.h> #include<process.h> using namespace std; int iTickets = 100; CRITICAL_SECTION g_cs; //A窗口 DWORD WINAPI SellTicketA(void* lpParam) { while (1) { EnterCriticalSection(&g_cs);//進(jìn)入臨界區(qū) if (iTickets > 0) { Sleep(1); iTickets--; printf("A買(mǎi)了一張票,剩余票數(shù)為:%d\n", iTickets); LeaveCriticalSection(&g_cs); } else { LeaveCriticalSection(&g_cs); break; } } return 0; } //B窗口 DWORD WINAPI SellTicketB(void* lpParam) { while (1) { EnterCriticalSection(&g_cs); if (iTickets > 0) { Sleep(1); iTickets--; printf("B買(mǎi)了一張票,剩余票數(shù)為:%d\n", iTickets); LeaveCriticalSection(&g_cs); } else { LeaveCriticalSection(&g_cs); break; } } return 0; } int main() { HANDLE hThreadA, hThreadB; hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL); hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL); CloseHandle(hThreadA); CloseHandle(hThreadB); InitializeCriticalSection(&g_cs);//初始化關(guān)鍵代碼 Sleep(1000); DeleteCriticalSection(&g_cs); system("pause"); return 0; }
到此這篇關(guān)于C++實(shí)現(xiàn)線(xiàn)程同步的四種方式總結(jié)的文章就介紹到這了,更多相關(guān)C++線(xiàn)程同步方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
概率的問(wèn)題:使用遞歸與多次試驗(yàn)?zāi)M的分析
以下對(duì)概率的問(wèn)題:使用了遞歸和多次試驗(yàn)?zāi)M。需要的朋友參考下2013-05-05如何通過(guò)指針突破C++類(lèi)的訪(fǎng)問(wèn)權(quán)限
這篇文章主要介紹了通過(guò)指針突破C++類(lèi)的訪(fǎng)問(wèn)權(quán)限,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02關(guān)于C++智能指針shared_ptr和unique_ptr能否互轉(zhuǎn)問(wèn)題
C++中的智能指針最常用的是shared_ptr和unique_ptr,C++新手最常問(wèn)的問(wèn)題是我從一個(gè)函數(shù)中拿到unique_ptr,但要轉(zhuǎn)成shared_ptr才能使用,要怎么轉(zhuǎn)換?同理是否能將shared_ptr轉(zhuǎn)換成unique_ptr,面對(duì)這些問(wèn)題,跟隨小編一起看看吧2022-05-05C語(yǔ)言數(shù)組越界引發(fā)的死循環(huán)問(wèn)題解決
本文主要介紹了C語(yǔ)言數(shù)組越界引發(fā)的死循環(huán)問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08C++強(qiáng)制類(lèi)型轉(zhuǎn)換的四種方式
本文主要介紹了C++強(qiáng)制類(lèi)型轉(zhuǎn)換的四種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05C語(yǔ)言的動(dòng)態(tài)內(nèi)存分配及動(dòng)態(tài)內(nèi)存分配函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言的動(dòng)態(tài)內(nèi)存分配及動(dòng)態(tài)內(nèi)存分配函數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03怎么通過(guò)C語(yǔ)言自動(dòng)生成MAC地址
以下是對(duì)使用C語(yǔ)言自動(dòng)生成MAC地址的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-09-09