「C++ 多线程」信号量 counting_semaphore 与 binary_semaphore
文章大图来源:pixiv_id=114949296
1 信号量
1.1 基本概念
多线程编程中,信号量 是用于控制对公共资源的访问、协调多个线程或进程的一种重要同步工具。它是 一种计数器,用来 限制资源被并发访问的线程数量。
1.2 计数信号量
计数信号量(Counting Semaphore)与传统的互斥锁不同,其允许多个线程同时访问相同的资源。
计数信号量 初始值为可用资源的数量,当线程获取资源时,信号量减一;释放资源时,信号量加一。
如果信号量的值达到零,表示没有可用资源,新的线程请求会被阻塞直到资源被释放。
1.3 二进制信号量
二进制信号量(Binary Semaphore)有点类似于传统互斥锁的功能,其值只有 0 和 1。
主要用于实现互斥访问,一个线程获取资源后,其他线程必须等待,直到资源被释放。
2 std::count_semaphore
2.1 基本概念
std::counting_semaphore
于 C++20 引入,位于位于 <semaphore>
头文件中。
std::counting_semaphore
允许多个线程同时访问一个资源。在多线程编程中,它用于限制可同时访问资源的最大线程数量。
std::counting_semaphore
是一个模板类,构造时需要指定最大计数值,即能够同时访问资源的线程数。
2.2 常规操作
-
acquire()
尝试减少信号量的计数值。
-
如果当前计数值为 0,则线程会被阻塞,直到有其他线程调用
release()
并增加计数值; -
如果当前计数值不为 0,则还运行当前计数值数量的线程同时访问共享数据。
-
-
release()
增加信号量的计数值。可能会唤醒一个或多个正在等待资源的线程。
2.3 代码示例
1 |
|
可能的运行结果如下:
1 |
|
从上面的结果可以看出,一开始线程 0 和线程 3 同时访问资源,其他线程处于阻塞状态;然后线程 4 和线程 1 同时访问资源,线程 2 被阻塞;最后线程 2 访问资源。
2.4 应用场景
- 管理多个线程访问少量的共享资源。
- 控制线程池中活跃线程的数量。
- 防止过多的线程同时访问导致资源枯竭或系统不稳定。
3 std::binary_semaphore
3.1 基本概念
std::binary_semaphore
是一种同步原语,在 C++20 中引入,位于 <semaphore>
头文件中。
std::binary_semaphore
实际上是 std::counting_semaphore
的一种特化,最大计数为 1,其只有两个状态,0(未锁定)和1(锁定)功能,类似于一个简易的互斥锁(std::mutex)。
主要用途是在线程之间协调共享资源的访问,确保只有一个线程在某一时刻访问资源,类似于互斥锁(mutex),但语义稍有不同。
3.2 常规操作
只能用初始计数为 0 或 1 来初始化。
0
:信号量初始时是锁定的,所以第一次acquire()
调用将被阻塞。1
:信号量初始时是未锁定的,所以第一次acquire()
调用将不会被阻塞。
3.3 代码示例
1 |
|
在上面的代码中,这里定义了两个二进制信号量 sm1
和 sm2
,初始计数都为0,这意味着它们开始都是在“锁定”状态。
执行顺序如下:
- 主线程启动子线程。
- 主线程通过
sm1.release()
向子线程发出信号。 - 子线程从
sm1.acquire()
中解除阻塞,开始其任务。 - 子线程完成任务后,通过
sm2.release()
向主线程发送完成信号。 - 主线程从
sm2.acquire()
中解除阻塞,继续执行。
可能的运行结果如下:
1 |
|
可以看出通过信号量机制,子线程确保在主线程发出信号执行,并在主线程尝试获取信号时执行完毕。
3.4 优势
-
效率:由于其轻量级的特性,
std::binary_semaphore
比互斥锁在某些情况下 更高效。 -
适用性:非常适合控制单次访问的资源,尤其是在线程之间进行信号传递时,与条件变量类似,但通常更易于使用。