C++中Semaphore內(nèi)核對象用法實例
信號量 (semaphore) 是一種輕量的同步原件,用于制約對共享資源的并發(fā)訪問。在可以使用兩者時,信號量能比條件變量更有效率。1
下面是在 www.open-std.org 對 C++20 semaphore 的一點介紹內(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)用于實現(xiàn)其他同步原語,并且,只要兩者都適用,就可以比條件變量更有效。
計數(shù)信號量是模擬非負資源計數(shù)的信號量對象。二進制信號量是一個只有兩種狀態(tài)的信號量對象,也稱為可用和不可用。[注意:二進制信號量應(yīng)該比使用單位數(shù)量級計數(shù)的計數(shù)信號量更有效。–尾注]
cppreference.com 中的標準庫頭文件 <semaphore> 中也給出了詳細定義。3。
C++20 中提供了兩個信號量類。(其實binary_semaphore僅僅是counting_semaphore的一個特例。)
| 信號量類名 | 含義 |
|---|---|
| counting_semaphore | 實現(xiàn)非負資源計數(shù)的信號量 |
| binary_semaphore | 僅擁有二個狀態(tài)的信號量 |
cppreference.com中給出的關(guān)于semaphore的定義如下:
// 概要
namespace std {
template<ptrdiff_t LeastMaxValue = /* 實現(xiàn)定義 */>
class counting_semaphore;
using binary_semaphore = counting_semaphore<1>;
}
// 類模板 std::counting_semaphore
namespace std {
template<ptrdiff_t LeastMaxValue = /* 實現(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 允許同一資源有多于一個同時訪問,至少允許 LeastMaxValue 個同時的訪問者若LeastMaxValue 為負則程序為謬構(gòu)。
binary_semaphore 是 std::counting_semaphore 的特化的別名,其 LeastMaxValue 為 1 。實現(xiàn)可能將 binary_semaphore 實現(xiàn)得比 std::counting_semaphore 的默認實現(xiàn)更高效。
counting_semaphore 含有由構(gòu)造函數(shù)初始化的內(nèi)部計數(shù)器。由調(diào)用 acquire() 與相關(guān)方法減少此計數(shù)器,而它通過調(diào)用 release() 增加。計數(shù)器為零時, acquire() 阻塞該計數(shù)器直至它增加,但 try_acquire() 不阻塞; try_acquire_for() 與 try_acquire_until() 阻塞直至計數(shù)器增加或到達時限。
類似 std::condition_variable 的 wait() , counting_semaphore 的 try_acquire() 可能虛假地失敗。
std::counting_semaphore的主要接口
| 接口 | 含義 |
|---|---|
| release | 增加內(nèi)部計數(shù)器并除阻獲取者 |
| acquire | 減少內(nèi)部計數(shù)器或阻塞到直至能如此 |
| try_acquire | 嘗試減少內(nèi)部計數(shù)器而不阻塞 |
| try_acquire_for | 嘗試減少內(nèi)部計數(shù)器,至多阻塞一段時長 |
| try_acquire_until | 嘗試減少內(nèi)部計數(shù)器,阻塞直至一個時間點 |
| max | 返回內(nèi)部計數(shù)器的最大可能值(靜態(tài),常量) |
注解
如其名所示, LeastMaxValue 是最小的最大值,而非實際最大值。從而 max() 能產(chǎn)生大于 LeastMaxValue 的值。
不同于 std::mutex , counting_semaphore 不捆綁到執(zhí)行線程——能在不同于釋放信號量的線程獲取該信號量。能同時進行 counting_semaphore 上的所有操作而無需聯(lián)系到任何特定的執(zhí)行線程,除了不能同時執(zhí)行,但能在一個不同的線程上執(zhí)行析構(gòu)函數(shù)。
信號量亦常用于發(fā)信/提醒而非互斥,通過初始化該信號量為 ?0? 從而阻塞嘗試 acquire() 的接收者,直至提醒者通過調(diào)用 release(n) “發(fā)信”。在此方面可把信號量當作 std::condition_variable 的替用品,通常它有更好的性能。
semaphore、mutex、condition_variable的區(qū)別
信號量 (semaphore) 是一種輕量的同步原件,用于制約對共享資源的并發(fā)訪問。在可以使用兩者時,信號量能比條件變量更有效率。
互斥(mutex)算法避免多個線程同時訪問共享資源。這會避免數(shù)據(jù)競爭,并提供線程間的同步支持。
條件變量(condition_variable)是允許多個線程相互交流的同步原語。它允許一定量的線程等待(可以定時)另一線程的提醒,然后再繼續(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是可以獨立使用的;condition_variable必須和mutex配對使用。
3: semaphore一般用于控制多個并發(fā)資源的訪問或者控制并行數(shù)量;mutex一般是起到同步訪問一個資源的作用。同一時刻,mutex保護的資源只能被一個線程訪問;semaphore的保護對象上面是可以有多個線程在訪問的。mutex是同步,semaphore是并行。
4: 由于condition_variable和mutex結(jié)合使用,condition_variable更多是為了通知、順序之類的控制。
5: C++語言中的mutex、semaphore、condition和系統(tǒng)級的概念不同。都是線程級別的,也就是不能跨進程控制的。要區(qū)別于windows api的 mutex、semaphore、event。windows系統(tǒng)上這幾個api創(chuàng)建有名對象時,是進程級別的。
C++中Semaphore內(nèi)核對象的用法
// Semaphore.cpp : 定義控制臺應(yīng)用程序的入口點。?
//?
?
#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)用方式,下面都有示例代碼。
注意:有個點容易遺忘的:當semop的實參sops設(shè)置>0的操作時,一般要給這個op動作添加SEM_UNDO標志,詳情可參考另一篇博文:linux線程通信之信號量。
應(yīng)用情景一:用信號量打造一個二值信號量(互斥量),也即:任何時刻只允許一個線程訪問共享資源。P操作用于占用資源,V操作代表釋放資源。
使用信號量,關(guān)鍵是要知道semop函數(shù)的特性:
① semop函數(shù)的第二形參sops可以以數(shù)組地址的形式輸入多個動作(稱為動作集),man手冊上講,semop函數(shù)會按照sops數(shù)組的順序、原子性的執(zhí)行動作集中的動作,要么一個都不執(zhí)行,要么全部執(zhí)行。手冊上說的這句話我覺得是有點問題的,“要么全部執(zhí)行”這句話實際上有個例外:如果動作集中某個動作設(shè)置的條件(如等待0)會使得線程堵塞在本函數(shù)中(或者本函數(shù)出錯返回)的話,那么后面的動作就只能等解除堵塞之后才能被執(zhí)行(堵塞時),或者得不到執(zhí)行(semop出錯返回時)。
② 如果在某時刻有多個線程都在等待互斥信號量的使用權(quán),那么一旦占用該互斥量的線程把它釋放后,這多個等待的線程中,只能有一個線程被解除堵塞
ps:當無法獲得信號量資源時,semop到底是堵塞,還是設(shè)置錯誤并返回,取決于第四參數(shù)是否或了IPC_NOWAIT標志。
方法1:
步驟:
(1) 把信號量的值初始化為0(創(chuàng)建信號量之后默認值就是0,該步驟不做也行)
(2) P操作,用semop函數(shù)設(shè)置線程等待信號量的值semval為0,若不為0則堵塞或報錯;然后用semop函數(shù)把信號量的值+1(也即:semval為0時可以立即通過,否則就要等待)。本步驟中的兩個動作,必須通過semop的實參一次把兩個動作都輸入進去,而不能分別調(diào)用兩次semop來實現(xiàn)。
(3) V操作,用semop函數(shù)設(shè)置信號值-1,注意:只有信號量值≥abs(-1)時,才能夠立即減1后立即返回,否則本線程又得等待,直到信號量值≥abs(-1)。當然,因為P操作已經(jīng)把信號量值+1了,所以這里信號量值肯定是≥abs(-1)。
分析:①整個程序中首次執(zhí)行P操作的時候,情況是怎樣的?看步驟(2),設(shè)置本線程為等待semval變?yōu)?,因為semval被初始化為0了,所以semop會立即返回或者繼續(xù)執(zhí)行形參指定的下一個動作:把semval+1。+1這種動作永不堵塞,于是本線程將繼續(xù)向下執(zhí)行開始訪問共享資源。
②當某個線程A執(zhí)行P之后,尚未V之前,又有另個線程B開始執(zhí)行P了,情況是怎樣的?還是看步驟(2),設(shè)置本線程B為等待semval變?yōu)?,因為線程A已經(jīng)把semval設(shè)為1了,于是線程B被堵塞或報錯。
③ 為什么步驟(2)中不允許把等待0和+1這兩個動作分別用兩次semop來實現(xiàn)?試想這樣一種情形:semval初始化為0,當進程A等待0時,發(fā)現(xiàn)確實是0,于是繼續(xù)向下執(zhí)行semop的+1(進而開始訪問共享資源),這時發(fā)生了進程/線程調(diào)度,切入了線程B,線程B恰好也要執(zhí)行P操作,等待semval變?yōu)?,也發(fā)現(xiàn)確實是0,于是繼續(xù)向下執(zhí)行semop的+1 (進而開始訪問共享資源),顯然,沒有達到預(yù)想的互斥的效果。semop函數(shù)提供了一種機制,把多個動作(稱為動作集)通過形參一次性傳入進去之后,操作系統(tǒng)可以保證,這些動作要么一個也不執(zhí)行,要么全部被執(zhí)行(除非:如果某個動作設(shè)置的條件會堵塞線程時,等堵塞解除后,后面的動作才會執(zhí)行),這就杜絕了這一問題。
方法2:
步驟:
① 用semctl或者semop把信號量值初始化為1
② P操作,用semop函數(shù)設(shè)置線程等待,直到semval≥abs(-1)才解除等待(也即,semval≥1時可以立即通過,否則就要等待);
③ V操作,用semop函數(shù)設(shè)置semval+1
分析:①整個程序中首次執(zhí)行P操作的時候,情況是怎樣的?看步驟(2),因為semval被初始化為1了,故本次P操作并不堵塞或出錯,而是把semval-1后直接返回,P操作完成后semval就變成0了;
②當某個線程A執(zhí)行P之后,尚未V之前,又有另個線程B開始執(zhí)行P了,情況是怎樣的?還是看步驟(2),設(shè)置本線程B為等待等待semval≥abs(-1),因為線程A的P已經(jīng)把semval設(shè)為0了,于是線程B被堵塞或報錯;
應(yīng)用情景二:例如,某個資源最多只允許5個線程同時訪問
這種應(yīng)用場景的一個更貼近生活的例子:某開水房的水管上(這跟水管就是個共享資源),只有5個水龍頭,那么這跟水管最多只允許5個人同時打水。每來一個人打水,信號量減1,每走一個人,信號量+1,也即:只要空閑水龍頭的數(shù)目(信號量)≥1,就可以放人進來打水,否則,都得排隊等。
這種應(yīng)用情景的處理方法,和上面提到的方法(2)是一樣的,唯一的區(qū)別就是初始化時,要信號量的值初始化為n:
方法3:
步驟:
① 用semctl或者semop把信號量值初始化為5;
② P操作,用semop函數(shù)設(shè)置線程等待,直到semval≥abs(-1)才解除等待(也即,semval≥1時可以立即通過,否則就要等待);
③ V操作,用semop函數(shù)設(shè)置semval+1;
分析: 程序把信號量值初始化為5以后,同時有13個線程發(fā)起了P操作,請求訪問共享資源,這時情況是怎樣的?名義上是同時,實際上在該信號量的信息維護鏈表中,發(fā)起P操作的線程仍然是有先后的,第一名開始執(zhí)行P,信號量發(fā)現(xiàn)自己的值是5,可以滿足第一名提出的semval≥abs(-1)無需等待條件,于是第一名無需等待,P操作直接返回(<的操作返回時會把信號量值減掉),從而可以訪問共享資源了;第二名線程開始執(zhí)行P操作,信號量發(fā)現(xiàn)自己是4,也可以滿足不等待的條件semval≥abs(-1)······,也即,前5名線程執(zhí)行P操作,完全不用等待,都可以直接獲得共享資源,而其余的13-7=7個線程執(zhí)行P操作會被阻塞。前5名當中,一旦有其中一個用完了資源并釋放了資源(執(zhí)行V操作)之后,那么第6名線程就會解除等待,從而獲得共享資源的訪問權(quán)。
希望本文所述對大家的C++程序設(shè)計有所幫助。
相關(guān)文章
C++/STL實現(xiàn)判斷平面內(nèi)兩條線段的位置關(guān)系代碼示例
這篇文章主要介紹了C++/STL實現(xiàn)判斷平面內(nèi)兩條線段的位置關(guān)系代碼示例,具有一定參考價值,需要的朋友可以了解下。2017-11-11

