C++中Semaphore內(nèi)核對象用法實(shí)例
信號量 (semaphore) 是一種輕量的同步原件,用于制約對共享資源的并發(fā)訪問。在可以使用兩者時(shí),信號量能比條件變量更有效率。1
下面是在 www.open-std.org 對 C++20 semaphore 的一點(diǎn)介紹內(nèi)容。(semaphores、latch、barrier)
Semaphores are lightweight synchronization primitives used to constrain concurrent access to a shared resource. They are widely used to implement other synchronization primitives and, whenever both are applicable, can be more efficient than condition variables.
A counting semaphore is a semaphore object that models a non-negative resource count. A binary semaphore is a semaphore object that has only two states, also known as available and unavailable. [ Note: A binary semaphore should be more efficient than a counting semaphore with a unit magnitude count. – end note ]
信號量是用于限制對共享資源的并發(fā)訪問的輕量級同步原語。它們被廣泛應(yīng)用于實(shí)現(xiàn)其他同步原語,并且,只要兩者都適用,就可以比條件變量更有效。
計(jì)數(shù)信號量是模擬非負(fù)資源計(jì)數(shù)的信號量對象。二進(jìn)制信號量是一個只有兩種狀態(tài)的信號量對象,也稱為可用和不可用。[注意:二進(jìn)制信號量應(yīng)該比使用單位數(shù)量級計(jì)數(shù)的計(jì)數(shù)信號量更有效。–尾注]
cppreference.com 中的標(biāo)準(zhǔn)庫頭文件 <semaphore> 中也給出了詳細(xì)定義。3。
C++20 中提供了兩個信號量類。(其實(shí)binary_semaphore僅僅是counting_semaphore的一個特例。)
信號量類名 | 含義 |
---|---|
counting_semaphore | 實(shí)現(xiàn)非負(fù)資源計(jì)數(shù)的信號量 |
binary_semaphore | 僅擁有二個狀態(tài)的信號量 |
cppreference.com中給出的關(guān)于semaphore的定義如下:
// 概要 namespace std { template<ptrdiff_t LeastMaxValue = /* 實(shí)現(xiàn)定義 */> class counting_semaphore; using binary_semaphore = counting_semaphore<1>; } // 類模板 std::counting_semaphore namespace std { template<ptrdiff_t LeastMaxValue = /* 實(shí)現(xiàn)定義 */> class counting_semaphore { public: static constexpr ptrdiff_t max() noexcept; constexpr explicit counting_semaphore(ptrdiff_t desired); ~counting_semaphore(); counting_semaphore(const counting_semaphore&) = delete; counting_semaphore& operator=(const counting_semaphore&) = delete; void release(ptrdiff_t update = 1); void acquire(); bool try_acquire() noexcept; template<class Rep, class Period> bool try_acquire_for(const chrono::duration<Rep, Period>& rel_time); template<class Clock, class Duration> bool try_acquire_until(const chrono::time_point<Clock, Duration>& abs_time); private: ptrdiff_t counter; // 僅用于闡釋 }; }
std::counting_semaphore4
counting_semaphore 是一個輕量同步元件,能控制對共享資源的訪問。不同于 std::mutex 、 counting_semaphore 允許同一資源有多于一個同時(shí)訪問,至少允許 LeastMaxValue 個同時(shí)的訪問者若LeastMaxValue 為負(fù)則程序?yàn)橹嚇?gòu)。
binary_semaphore 是 std::counting_semaphore 的特化的別名,其 LeastMaxValue 為 1 。實(shí)現(xiàn)可能將 binary_semaphore 實(shí)現(xiàn)得比 std::counting_semaphore 的默認(rèn)實(shí)現(xiàn)更高效。
counting_semaphore 含有由構(gòu)造函數(shù)初始化的內(nèi)部計(jì)數(shù)器。由調(diào)用 acquire() 與相關(guān)方法減少此計(jì)數(shù)器,而它通過調(diào)用 release() 增加。計(jì)數(shù)器為零時(shí), acquire() 阻塞該計(jì)數(shù)器直至它增加,但 try_acquire() 不阻塞; try_acquire_for() 與 try_acquire_until() 阻塞直至計(jì)數(shù)器增加或到達(dá)時(shí)限。
類似 std::condition_variable 的 wait() , counting_semaphore 的 try_acquire() 可能虛假地失敗。
std::counting_semaphore的主要接口
接口 | 含義 |
---|---|
release | 增加內(nèi)部計(jì)數(shù)器并除阻獲取者 |
acquire | 減少內(nèi)部計(jì)數(shù)器或阻塞到直至能如此 |
try_acquire | 嘗試減少內(nèi)部計(jì)數(shù)器而不阻塞 |
try_acquire_for | 嘗試減少內(nèi)部計(jì)數(shù)器,至多阻塞一段時(shí)長 |
try_acquire_until | 嘗試減少內(nèi)部計(jì)數(shù)器,阻塞直至一個時(shí)間點(diǎn) |
max | 返回內(nèi)部計(jì)數(shù)器的最大可能值(靜態(tài),常量) |
注解
如其名所示, LeastMaxValue 是最小的最大值,而非實(shí)際最大值。從而 max() 能產(chǎn)生大于 LeastMaxValue 的值。
不同于 std::mutex , counting_semaphore 不捆綁到執(zhí)行線程——能在不同于釋放信號量的線程獲取該信號量。能同時(shí)進(jìn)行 counting_semaphore 上的所有操作而無需聯(lián)系到任何特定的執(zhí)行線程,除了不能同時(shí)執(zhí)行,但能在一個不同的線程上執(zhí)行析構(gòu)函數(shù)。
信號量亦常用于發(fā)信/提醒而非互斥,通過初始化該信號量為 ?0? 從而阻塞嘗試 acquire() 的接收者,直至提醒者通過調(diào)用 release(n) “發(fā)信”。在此方面可把信號量當(dāng)作 std::condition_variable 的替用品,通常它有更好的性能。
semaphore、mutex、condition_variable的區(qū)別
信號量 (semaphore) 是一種輕量的同步原件,用于制約對共享資源的并發(fā)訪問。在可以使用兩者時(shí),信號量能比條件變量更有效率。
互斥(mutex)算法避免多個線程同時(shí)訪問共享資源。這會避免數(shù)據(jù)競爭,并提供線程間的同步支持。
條件變量(condition_variable)是允許多個線程相互交流的同步原語。它允許一定量的線程等待(可以定時(shí))另一線程的提醒,然后再繼續(xù)。條件變量始終關(guān)聯(lián)到一個互斥。
1: semaphore對acquire和release操作沒有限制,可以在不同線程操作;可以僅在線程A里面acquire,僅在線程B里面release。
mutex的lock和unlock必須在同一個線程配對使用;也就是說線程A內(nèi)mutex如果lock了,必須在線程A內(nèi)unlock,線程B內(nèi)lock了,也必須在線程B內(nèi)unlock。
2: semaphore和mutex是可以獨(dú)立使用的;condition_variable必須和mutex配對使用。
3: semaphore一般用于控制多個并發(fā)資源的訪問或者控制并行數(shù)量;mutex一般是起到同步訪問一個資源的作用。同一時(shí)刻,mutex保護(hù)的資源只能被一個線程訪問;semaphore的保護(hù)對象上面是可以有多個線程在訪問的。mutex是同步,semaphore是并行。
4: 由于condition_variable和mutex結(jié)合使用,condition_variable更多是為了通知、順序之類的控制。
5: C++語言中的mutex、semaphore、condition和系統(tǒng)級的概念不同。都是線程級別的,也就是不能跨進(jìn)程控制的。要區(qū)別于windows api的 mutex、semaphore、event。windows系統(tǒng)上這幾個api創(chuàng)建有名對象時(shí),是進(jìn)程級別的。
C++中Semaphore內(nèi)核對象的用法
// Semaphore.cpp : 定義控制臺應(yīng)用程序的入口點(diǎn)。? //? ? #include "stdafx.h"? #include <Windows.h>? #include <process.h>?? ? HANDLE g_hSemaphore;? DWORD g_nConut1 = 0;? DWORD g_nConut2 = 0;? unsigned __stdcall ThreadProc1( void* pArguments )? {? ??? ::WaitForSingleObject(g_hSemaphore, INFINITE);? ??? for (int i=0;i<10000;i++)? ??? {? ??????? g_nConut1++;? ??????? g_nConut2++;? ??? }? ??? ::ReleaseSemaphore(g_hSemaphore, 1, NULL);? ??? printf("ThreadProc1\n");? ??? return 0;? }? ? unsigned __stdcall ThreadProc2( void* pArguments )? {? ??? ::WaitForSingleObject(g_hSemaphore, INFINITE);? ??? for (int i=0;i<10000;i++)? ??? {? ??????? g_nConut1++;? ??????? g_nConut2++;? ??? }? ??? ::ReleaseSemaphore(g_hSemaphore, 1, NULL);? ??? printf("ThreadProc2\n");? ??? return 0;? }? ? unsigned __stdcall ThreadProc3( void* pArguments )? {? ??? ::WaitForSingleObject(g_hSemaphore, INFINITE);? ??? for (int i=0;i<10000;i++)? ??? {? ??????? g_nConut1++;? ??????? g_nConut2++;? ??? }? ??? ::ReleaseSemaphore(g_hSemaphore, 1, NULL);? ??? printf("ThreadProc3\n");? ??? return 0;? }? int _tmain(int argc, _TCHAR* argv[])? {? ??? g_hSemaphore = ::CreateSemaphore(NULL, 2, 2, NULL);? ??? HANDLE hThread[3];? ??? hThread[0] = (HANDLE)::_beginthreadex(NULL, 0, ThreadProc1, NULL, 0, NULL);? ??? hThread[1] = (HANDLE)::_beginthreadex(NULL, 0, ThreadProc2, NULL, 0, NULL);? ??? hThread[2] = (HANDLE)::_beginthreadex(NULL, 0, ThreadProc3, NULL, 0, NULL);? ? ??? ::WaitForMultipleObjects(2,hThread,TRUE, INFINITE);? ??? printf("g_count1=%d\n", g_nConut1);? ??? printf("g_count2=%d\n", g_nConut2);? ??? printf("main finished.\n");? ??? return 0;? }
linux信號量semaphore的幾種使用方法
以下提到的幾種應(yīng)用方式,下面都有示例代碼。
注意:有個點(diǎn)容易遺忘的:當(dāng)semop的實(shí)參sops設(shè)置>0的操作時(shí),一般要給這個op動作添加SEM_UNDO標(biāo)志,詳情可參考另一篇博文:linux線程通信之信號量。
應(yīng)用情景一:用信號量打造一個二值信號量(互斥量),也即:任何時(shí)刻只允許一個線程訪問共享資源。P操作用于占用資源,V操作代表釋放資源。
使用信號量,關(guān)鍵是要知道semop函數(shù)的特性:
① semop函數(shù)的第二形參sops可以以數(shù)組地址的形式輸入多個動作(稱為動作集),man手冊上講,semop函數(shù)會按照sops數(shù)組的順序、原子性的執(zhí)行動作集中的動作,要么一個都不執(zhí)行,要么全部執(zhí)行。手冊上說的這句話我覺得是有點(diǎn)問題的,“要么全部執(zhí)行”這句話實(shí)際上有個例外:如果動作集中某個動作設(shè)置的條件(如等待0)會使得線程堵塞在本函數(shù)中(或者本函數(shù)出錯返回)的話,那么后面的動作就只能等解除堵塞之后才能被執(zhí)行(堵塞時(shí)),或者得不到執(zhí)行(semop出錯返回時(shí))。
② 如果在某時(shí)刻有多個線程都在等待互斥信號量的使用權(quán),那么一旦占用該互斥量的線程把它釋放后,這多個等待的線程中,只能有一個線程被解除堵塞
ps:當(dāng)無法獲得信號量資源時(shí),semop到底是堵塞,還是設(shè)置錯誤并返回,取決于第四參數(shù)是否或了IPC_NOWAIT標(biāo)志。
方法1:
步驟:
(1) 把信號量的值初始化為0(創(chuàng)建信號量之后默認(rèn)值就是0,該步驟不做也行)
(2) P操作,用semop函數(shù)設(shè)置線程等待信號量的值semval為0,若不為0則堵塞或報(bào)錯;然后用semop函數(shù)把信號量的值+1(也即:semval為0時(shí)可以立即通過,否則就要等待)。本步驟中的兩個動作,必須通過semop的實(shí)參一次把兩個動作都輸入進(jìn)去,而不能分別調(diào)用兩次semop來實(shí)現(xiàn)。
(3) V操作,用semop函數(shù)設(shè)置信號值-1,注意:只有信號量值≥abs(-1)時(shí),才能夠立即減1后立即返回,否則本線程又得等待,直到信號量值≥abs(-1)。當(dāng)然,因?yàn)镻操作已經(jīng)把信號量值+1了,所以這里信號量值肯定是≥abs(-1)。
分析:①整個程序中首次執(zhí)行P操作的時(shí)候,情況是怎樣的?看步驟(2),設(shè)置本線程為等待semval變?yōu)?,因?yàn)閟emval被初始化為0了,所以semop會立即返回或者繼續(xù)執(zhí)行形參指定的下一個動作:把semval+1。+1這種動作永不堵塞,于是本線程將繼續(xù)向下執(zhí)行開始訪問共享資源。
②當(dāng)某個線程A執(zhí)行P之后,尚未V之前,又有另個線程B開始執(zhí)行P了,情況是怎樣的?還是看步驟(2),設(shè)置本線程B為等待semval變?yōu)?,因?yàn)榫€程A已經(jīng)把semval設(shè)為1了,于是線程B被堵塞或報(bào)錯。
③ 為什么步驟(2)中不允許把等待0和+1這兩個動作分別用兩次semop來實(shí)現(xiàn)?試想這樣一種情形:semval初始化為0,當(dāng)進(jìn)程A等待0時(shí),發(fā)現(xiàn)確實(shí)是0,于是繼續(xù)向下執(zhí)行semop的+1(進(jìn)而開始訪問共享資源),這時(shí)發(fā)生了進(jìn)程/線程調(diào)度,切入了線程B,線程B恰好也要執(zhí)行P操作,等待semval變?yōu)?,也發(fā)現(xiàn)確實(shí)是0,于是繼續(xù)向下執(zhí)行semop的+1 (進(jìn)而開始訪問共享資源),顯然,沒有達(dá)到預(yù)想的互斥的效果。semop函數(shù)提供了一種機(jī)制,把多個動作(稱為動作集)通過形參一次性傳入進(jìn)去之后,操作系統(tǒng)可以保證,這些動作要么一個也不執(zhí)行,要么全部被執(zhí)行(除非:如果某個動作設(shè)置的條件會堵塞線程時(shí),等堵塞解除后,后面的動作才會執(zhí)行),這就杜絕了這一問題。
方法2:
步驟:
① 用semctl或者semop把信號量值初始化為1
② P操作,用semop函數(shù)設(shè)置線程等待,直到semval≥abs(-1)才解除等待(也即,semval≥1時(shí)可以立即通過,否則就要等待);
③ V操作,用semop函數(shù)設(shè)置semval+1
分析:①整個程序中首次執(zhí)行P操作的時(shí)候,情況是怎樣的?看步驟(2),因?yàn)閟emval被初始化為1了,故本次P操作并不堵塞或出錯,而是把semval-1后直接返回,P操作完成后semval就變成0了;
②當(dāng)某個線程A執(zhí)行P之后,尚未V之前,又有另個線程B開始執(zhí)行P了,情況是怎樣的?還是看步驟(2),設(shè)置本線程B為等待等待semval≥abs(-1),因?yàn)榫€程A的P已經(jīng)把semval設(shè)為0了,于是線程B被堵塞或報(bào)錯;
應(yīng)用情景二:例如,某個資源最多只允許5個線程同時(shí)訪問
這種應(yīng)用場景的一個更貼近生活的例子:某開水房的水管上(這跟水管就是個共享資源),只有5個水龍頭,那么這跟水管最多只允許5個人同時(shí)打水。每來一個人打水,信號量減1,每走一個人,信號量+1,也即:只要空閑水龍頭的數(shù)目(信號量)≥1,就可以放人進(jìn)來打水,否則,都得排隊(duì)等。
這種應(yīng)用情景的處理方法,和上面提到的方法(2)是一樣的,唯一的區(qū)別就是初始化時(shí),要信號量的值初始化為n:
方法3:
步驟:
① 用semctl或者semop把信號量值初始化為5;
② P操作,用semop函數(shù)設(shè)置線程等待,直到semval≥abs(-1)才解除等待(也即,semval≥1時(shí)可以立即通過,否則就要等待);
③ V操作,用semop函數(shù)設(shè)置semval+1;
分析: 程序把信號量值初始化為5以后,同時(shí)有13個線程發(fā)起了P操作,請求訪問共享資源,這時(shí)情況是怎樣的?名義上是同時(shí),實(shí)際上在該信號量的信息維護(hù)鏈表中,發(fā)起P操作的線程仍然是有先后的,第一名開始執(zhí)行P,信號量發(fā)現(xiàn)自己的值是5,可以滿足第一名提出的semval≥abs(-1)無需等待條件,于是第一名無需等待,P操作直接返回(<的操作返回時(shí)會把信號量值減掉),從而可以訪問共享資源了;第二名線程開始執(zhí)行P操作,信號量發(fā)現(xiàn)自己是4,也可以滿足不等待的條件semval≥abs(-1)······,也即,前5名線程執(zhí)行P操作,完全不用等待,都可以直接獲得共享資源,而其余的13-7=7個線程執(zhí)行P操作會被阻塞。前5名當(dāng)中,一旦有其中一個用完了資源并釋放了資源(執(zhí)行V操作)之后,那么第6名線程就會解除等待,從而獲得共享資源的訪問權(quán)。
希望本文所述對大家的C++程序設(shè)計(jì)有所幫助。
相關(guān)文章
C++中一維數(shù)組與指針的關(guān)系詳細(xì)總結(jié)
以下是對C++中一維數(shù)組與指針的關(guān)系進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過來參考下2013-09-09C語言學(xué)習(xí)之函數(shù)知識總結(jié)
函數(shù)是一組一起執(zhí)行一個任務(wù)的語句。每個?C?程序都至少有一個函數(shù),即主函數(shù)?main()?,所有簡單的程序都可以定義其他額外的函數(shù)。本文就為大家詳細(xì)講講C語言中函數(shù)的相關(guān)知識點(diǎn),希望有所幫助2022-07-07C++面試八股文之override和finial關(guān)鍵字有何作用
C++11中的override和final關(guān)鍵字是為了增強(qiáng)代碼的編譯時(shí)類型檢查和面向?qū)ο笤O(shè)計(jì)中的繼承機(jī)制,下面這篇文章主要給大家介紹了關(guān)于C++面試八股文之override和finial關(guān)鍵字有何作用的相關(guān)資料,需要的朋友可以參考下2023-06-06C語言以數(shù)據(jù)塊的形式讀寫文件實(shí)例代碼
本文主要介紹C語言中以數(shù)據(jù)塊的形式讀寫文件,這里提供了實(shí)例代碼舉例說明,有需要的小伙伴可以參考下2016-07-07C++標(biāo)準(zhǔn)庫中sstream與strstream的區(qū)別詳細(xì)解析
以下是對C++標(biāo)準(zhǔn)庫中sstream與strstream的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-09-09C++ 虛函數(shù)與純虛函數(shù)的使用與區(qū)別
本文主要介紹了C++ 虛函數(shù)與純虛函數(shù)的使用與區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08