一、引言
在多线程编程中,信号量(Semaphore)是一种重要的同步机制,用于控制对共享资源的访问。虽然C++标准库中没有直接提供信号量类,但我们可以利用C++11引入的<mutex>
和<condition_variable>
来实现一个高效的信号量。本文将详细介绍信号量的实现原理,并展示如何使用它构建经典的生产者-消费者模型。
二、信号量的基本概念
信号量是一种计数器,用于控制多个线程对共享资源的访问。它主要有两种操作:
- Wait() (也称为P操作):当信号量值大于0时,线程可以继续执行并将信号量减1;否则线程被阻塞;
- Signal() (也称为V操作):释放资源,将信号量值加1并唤醒等待的线程。
三、C++11信号量实现
头文件设计
// CSemaphore.h
#pragma once
#include <mutex>
#include <condition_variable>
class CSemaphore {
public:
explicit CSemaphore(long count = 0); // 使用explicit防止隐式转换
void Signal(); // V操作
void Wait(); // P操作
private:
std::mutex mutex_; // 互斥锁保护计数器
std::condition_variable cv_; // 条件变量用于线程等待
long count_; // 信号量计数器
};
实现细节
// CSemaphore.cpp
#include "CSemaphore.h"
// 构造函数初始化计数器
CSemaphore::CSemaphore(long count) :count_{count} {}
// Signal操作:增加计数器并通知一个等待线程
void CSemaphore::Signal() {
std::unique_lock<std::mutex> lock(mutex_);
++count_;
cv_.notify_one(); // 通知一个等待线程
}
// Wait操作:等待直到计数器大于0,然后减少计数器
void CSemaphore::Wait() {
std::unique_lock<std::mutex> lock(mutex_);
// 使用条件变量等待,直到count_ > 0
cv_.wait(lock, [=] {return m_count > 0; });
--count_;
}
实现要点说明
- 线程安全:使用
std::mutex
保护对count_
的访问,防止竞态条件; - 条件变量:
std::condition_variable
用于高效地等待信号量变为正数; - RAII:
std::unique_lock
自动管理锁的生命周期; - 虚假唤醒:条件变量的
wait
方法包含谓词检查,防止虚假唤醒。
四、生产者-消费者模型应用
下面展示如何使用我们实现的信号量构建一个生产者-消费者模型,解决经典的线程同步问题。
全局定义
#include "CSemaphore.h"
#include <iostream>
constexpr int BufferSize = 128; // 缓冲区大小
constexprint DataSize = 500; // 生产/消费数据总量
int buff[BufferSize]; // 共享缓冲区
CSemaphore freeSpace(BufferSize); // 空闲空间信号量(初始为缓冲区大小)
CSemaphore usedSpace(-100); // 已用空间信号量(初始为0)
生产者线程
void producer() {
for (int i = 0; i < DataSize; ++i) {
freeSpace.Wait(); // 等待有空闲空间
// 生产数据
std::thread::id thread_id = std::this_thread::get_id();
buff[i%BufferSize] = i;
printf("Thread %d,生产 %d\n", thread_id, i);
usedSpace.Signal(); // 通知有数据可用
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
消费者线程
void consumer() {
for (int i = 0; i < DataSize; ++i) {
usedSpace.Wait(); // 等待有数据可用
// 消费数据
std::thread::id thread_id = std::this_thread::get_id();
printf("Thread %d,消费的数字 %d\n", thread_id, buff[i%BufferSize]);
freeSpace.Signal(); // 通知有空闲空间
}
}
主函数
int main() {
std::thread threads1(producer);
std::thread threads2(consumer);
threads1.join();
threads2.join();
std::cout << "完成!" << std::endl;
std::cin.get();
return 0;
}