一、std::counting_semaphore
在前面介紹過C++20中的同步庫,其中就提到過std::counting_semaphore。但當時的重點是同步庫的整體介紹,本文則會對std::counting_semaphore這個信號量進行一個全面的分析和說明,并有針對性的給出具體的例程。
C++20中對其的定義為:
template< std::ptrdiff_t LeastMaxValue = /* implementation-defined */ >
class counting_semaphore;
using binary_semaphore = std::counting_semaphore<1>;
LeastMaxValue值并不實際最大值,即此值是可以被突破的。
二、應用說明
要想掌握好std::counting_semaphore,如果能夠在學習操作系統時將其中的PV原語理解的很透徹,那么這就不是什么問題了。所以推薦大家如果不清楚的可以回頭先看一看什么是PV原語。
std::counting_semaphore作為一個輕量級的同步原語,主要是用來對共享資源的控制,與互斥體等的不同,其允許對同一資源進行指定數量的線程同步訪問。怎么說呢,其實就是C++標準中的同步控制,在以前基本只能同一情況下訪問同一資源(其它庫如Posix,windows平臺等)。可能大牛們覺得已經夠用了,但在其它語言都推出了信號燈及其類似的同步原語,而且隨著C++應用的不斷發展,應該是大牛們覺得還是要提供一下類似的實現(這只是個人猜測)。
counting_semaphore提供了一個指定線程同步訪問的數量,而binary_semaphore則可以理解成前者的一個特化例子,只支持0,1兩種狀態,有點類似于互斥體,但它沒有所有權一說。這樣應用到一些特定的場景時不用調用復雜的counting_semaphore。
std::counting_semaphore與普通的Mutex一個重要的不同在于,與線程的所有權控制不同,前者不會綁定到指定的線程,也即任何一個線程都可以進行獲取和釋放信號量,請開發者一定要明白。
其最典型的兩個接口為:
1、std::counting_semaphore::release
原子性的按Update增加內部計數器,即增加信號量。當然update的值需要大于等0且小于等于最大值-當前計數器大小。
2、std::counting_semaphore::acquire
原子性的將內部計數器減1,前提是內部計數器大于0;否則將阻塞線程,直到計數器超過0。
這里有一個需要注意的情況,counting_semaphore在信號的下限即0時,調用acquire,不會出現異常問題;但在到達上限時,再調用release,則會出現UB行為。這點非常重要。
如果大家想擴展一下視野,還可以看看其它庫或平臺中的類似的semaphore的實現,也可以看看其它語言(如Java等)中類似的實現,就可以更好的理解信號量的應用。
三、例程
看一下cppreference上的例程:
#include <chrono>
#include <iostream>
#include <semaphore>
#include <thread>// global binary semaphore instances
// object counts are set to zero
// objects are in non-signaled state
std::binary_semaphoresmphSignalMainToThread{0},smphSignalThreadToMain{0};void ThreadProc()
{// wait for a signal from the main proc// by attempting to decrement the semaphoresmphSignalMainToThread.acquire();// this call blocks until the semaphore's count// is increased from the main procstd::cout << "[thread] Got the signal\n"; // response message// wait for 3 seconds to imitate some work// being done by the threadusing namespace std::literals;std::this_thread::sleep_for(3s);std::cout << "[thread] Send the signal\n"; // message// signal the main proc backsmphSignalThreadToMain.release();
}int main()
{// create some worker threadstd::thread thrWorker(ThreadProc);std::cout << "[main] Send the signal\n"; // message// signal the worker thread to start working// by increasing the semaphore's countsmphSignalMainToThread.release();// wait until the worker thread is done doing the work// by attempting to decrement the semaphore's countsmphSignalThreadToMain.acquire();std::cout << "[main] Got the signal\n"; // response messagethrWorker.join();
}
運行結果:
[main] Send the signal
[thread] Got the signal
[thread] Send the signal
[main] Got the signal
四、總結
大牛陳碩曾經說過,實際的編程場景基本用不到semaphore,如果真用到了,極有可能設計上有問題。他的這個說法本人非常贊同。建議在實際場景中盡量還是謹慎使用這種信號量,不過,在某些特定的場景下,如果確實是需要使用信號量,或者整體考慮代價要小于其它方法的情況下,也不是不可以使用。
仍然是前面反復提到的,合適的就是最好的。技術本身沒有絕對的好與壞!