C++20中的counting_semaphore的应用
一、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,如果真用到了,极有可能设计上有问题。他的这个说法本人非常赞同。建议在实际场景中尽量还是谨慎使用这种信号量,不过,在某些特定的场景下,如果确实是需要使用信号量,或者整体考虑代价要小于其它方法的情况下,也不是不可以使用。
仍然是前面反复提到的,合适的就是最好的。技术本身没有绝对的好与坏!