条件变量和信号量的区别
条件变量 vs 信号量
1. 条件变量 (std::condition_variable
)
📌 作用
- 适用于线程间的等待和通知机制,即一个线程等待某个条件成立,而另一个线程在条件满足时通知它。
- 需要与**互斥锁(
std::mutex
)**配合使用,以保证对共享资源的访问是安全的。
📌 工作方式
- 线程获取锁 (
std::unique_lock<std::mutex>
) - 使用
wait()
进入等待状态,自动释放锁 - 另一个线程修改条件后,调用
notify_one()
或notify_all()
进行通知 wait()
被唤醒后,重新获取锁,并检查条件是否满足,若满足则继续执行
📌 代码示例
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 释放锁并等待
std::cout << "Worker is running!\n";
}
int main() {
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(1));
{ // 作用域保证锁的正确释放
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 唤醒一个线程
t.join();
return 0;
}
📌 特性
✅ 等待时释放锁,避免死锁
✅ 适用于复杂条件的同步(例如等待任务队列不为空)
❌ 只能用于线程间的同步,不能跨进程
❌ 等待时可能会发生虚假唤醒(Spurious Wakeup),所以通常需要在 wait()
里重复检查条件
2. 信号量 (std::counting_semaphore
/ std::binary_semaphore
)
📌 作用
- 用于限制资源的访问数量,如控制线程池的并发数,或者控制对某个资源的访问次数。
- 不依赖互斥锁 (
std::mutex
),可以在多个线程或进程之间同步。
📌 工作方式
- 信号量有一个初始计数值(代表可用资源数量)
acquire()
(P 操作):等待资源可用,然后减少计数release()
(V 操作):增加计数,通知其他等待线程
📌 代码示例
#include <iostream>
#include <thread>
#include <semaphore> // C++20 引入的信号量
std::counting_semaphore<2> sem(2); // 允许最多2个线程访问
void worker(int id) {
sem.acquire(); // 请求资源
std::cout << "Worker " << id << " is running\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
sem.release(); // 释放资源
}
int main() {
std::thread t1(worker, 1);
std::thread t2(worker, 2);
std::thread t3(worker, 3);
t1.join();
t2.join();
t3.join();
return 0;
}
📌 特性
✅ 不会发生虚假唤醒
✅ 适用于资源管理(如限制并发数)
✅ 不依赖互斥锁,可用于跨进程同步
❌ 只能用于计数,不适用于复杂条件等待(如“任务队列不为空”这种条件)
3. 条件变量 vs 信号量
对比项 | 条件变量 (std::condition_variable ) | 信号量 (std::counting_semaphore ) |
---|---|---|
适用场景 | 线程间的等待和通知(如任务队列) | 资源访问控制(如线程池、流量控制) |
依赖互斥锁 | 需要 std::mutex | 不需要 std::mutex |
等待时是否释放锁 | 是,等待时释放锁 | 不适用(不依赖锁) |
是否可能虚假唤醒 | 是(必须在 wait() 里检查条件) | 不会发生 |
是否可用于跨进程 | 否 | 是(POSIX 信号量) |
适用于复杂条件 | 适用(如“任务队列不为空”) | 不适用(只能计数) |
4. 什么时候用条件变量,什么时候用信号量?
✅ 用 std::condition_variable
的情况
- 需要线程间等待某个特定条件(如等待队列不为空)
- 需要多个线程协调任务执行
- 需要等待某些任务完成(如生产者-消费者模型)
✅ 用 std::counting_semaphore
的情况
- 需要限制资源的并发访问数(如线程池)
- 需要控制某个操作的最大并发数(如数据库连接池)
- 需要跨进程同步(信号量可以在多个进程之间共享)
5. 总结
- 条件变量 适用于等待某个特定条件,适合任务队列、生产者-消费者模式。
- 信号量 适用于控制资源访问数量,适合线程池、流量控制、资源管理等场景。
如果你在写线程池,通常会用信号量来控制并发数,而在写任务队列时,通常会用条件变量来等待任务到来。