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