C++ 20 信号量详解
一、信号量类型
C++20 标准中定义了两种信号量:
std::counting_semaphore<Max>
:计数信号量(允许资源池最多有Max
个资源)std::binary_semaphore
:二进制信号量(等价于std::counting_semaphore<1>
)
二、代码实现与详解
1. 计数信号量(生产者-消费者模型)
#include <iostream>
#include <thread>
#include <semaphore>
#include <queue>
#include <mutex>
// 最大缓冲区大小
constexpr size_t BUFFER_SIZE = 5;
// 定义信号量(空位初始为5,数据初始为0)
std::counting_semaphore<BUFFER_SIZE> empty_slots(BUFFER_SIZE);
std::counting_semaphore<BUFFER_SIZE> data_items(0);
std::mutex mtx; // 保护共享队列的互斥锁
std::queue<int> buffer; // 共享缓冲区
bool producer_done = false; // 生产完成标志
void producer() {
for (int i = 1; i <= 10; ++i) {
empty_slots.acquire(); // 等待空位
{
std::lock_guard<std::mutex> lock(mtx);
buffer.push(i);
std::cout << "Product: " << i << std::endl;
}
data_items.release(); // 增加数据项
}
// 生产完成后设置标志
std::lock_guard<std::mutex> lock(mtx);
producer_done = true;
}
void consumer() {
while (true) {
data_items.acquire(); // 等待数据
{
std::lock_guard<std::mutex> lock(mtx);
// 检查是否所有数据已消费
if (producer_done && buffer.empty()) break;
int val = buffer.front();
buffer.pop();
std::cout << "Consume: " << val << std::endl;
}
empty_slots.release(); // 释放空位
}
}
int main() {
std::jthread prod(producer); // C++20 自动管理线程
std::jthread cons(consumer);
return 0;
}
2. 二进制信号量(互斥访问)
#include <iostream>
#include <thread>
#include <semaphore>
std::binary_semaphore resource(1); // 初始可用
int counter = 0;
void worker(int id) {
for (int i = 0; i < 3; ++i) {
resource.acquire(); // P操作
++counter;
std::cout << "线程" << id << "修改计数器: " << counter << std::endl;
resource.release(); // V操作
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
std::jthread t1(worker, 1);
std::thread t2(worker, 2);
t1.join();
t2.join();
return 0;
}
三、编译与运行
编译命令(需要支持C++20的编译器):
g++ -std=c++20 -pthread -o semaphore_demo semaphore_demo.cpp
输出示例:
四、核心概念解析
acquire()
(P操作):- 减少信号量计数器
- 若计数器为0则阻塞,直到有其他线程执行
release()
release()
(V操作):- 增加信号量计数器
- 唤醒等待中的线程(如果有)
二进制信号量特性:
- 初始值设为1时等价于互斥锁
- 但释放操作可由任意线程执行(与互斥锁不同)
五、关键点总结
特性 | 计数信号量 | 二进制信号量 |
---|---|---|
最大计数值 | 模板参数指定(如<5> ) |
固定为1 |
典型应用场景 | 资源池管理 | 互斥访问/同步标志 |
线程唤醒策略 | 先进先出(FIFO) | 取决于具体实现 |
内存占用 | 每个实例约4-8字节 | 同计数信号量 |