C++ 学习(8)——多线程(2)--线程通信与条件变量
好的,以下是你学习多线程的第 4 天课程设计:《线程通信:条件变量 std::condition_variable
》。这是进入线程协作阶段非常重要的一课,理解它之后你可以写出更复杂、智能的多线程程序,比如生产者-消费者模型、线程唤醒机制等。
📘 Day 4:线程通信:条件变量 std::condition_variable
一、课程目标
- 理解线程通信的意义
- 学会使用
std::condition_variable
实现线程间的等待与唤醒 - 能够编写基本的生产者-消费者模型
- 学会避免虚假唤醒(spurious wakeup)
- 掌握
wait()
、notify_one()
、notify_all()
的用法
二、线程通信的背景
前面我们通过 std::mutex
实现了互斥访问共享资源。但在很多真实场景中,线程之间需要等待某个条件成立才能执行,比如:
消费者线程必须等生产者生产完数据之后才能消费。
这就需要线程间通信机制,条件变量是最常用的方式之一。
三、条件变量简介
🔹 条件变量 std::condition_variable
它不是锁,而是一种通知机制:
- 一个线程可以等待某个条件成立(阻塞住自己);
- 另一个线程可以在条件成立后,唤醒等待的线程。
四、基本用法
示例:主线程等待子线程准备好数据
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock(mtx);
ready = true;
std::cout << "Worker: data is ready.\n";
cv.notify_one(); // 通知等待的线程
}
int main() {
std::thread t(worker);
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 等待 ready 为 true
std::cout << "Main: got the signal.\n";
t.join();
return 0;
}
五、核心函数说明
cv.wait(lock, predicate)
- 会释放
lock
并阻塞线程,直到收到通知; - 收到通知后,重新获取锁,并检查条件
predicate()
是否为真; - 重要:必须配合
std::unique_lock<std::mutex>
使用。
cv.notify_one()
- 唤醒一个在
wait()
上阻塞的线程(任意一个)。
cv.notify_all()
- 唤醒所有正在等待的线程。
六、虚假唤醒与谓词写法
🔸 虚假唤醒(spurious wakeup) 是指线程在没有 notify_xxx()
调用时也被唤醒。
因此,推荐使用带谓词版本的 wait()
:
cv.wait(lock, []{ return ready; }); // 推荐写法
这样就算是“假唤醒”,也会重新判断条件是否满足,确保程序逻辑正确。
七、实战:生产者-消费者模型
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
bool done = false;
void producer() {
for (int i = 1; i <= 5; ++i) {
std::unique_lock<std::mutex> lock(mtx);
q.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_one(); // 通知消费者
}
std::unique_lock<std::mutex> lock(mtx);
done = true;
cv.notify_all(); // 通知所有等待线程:生产结束
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !q.empty() || done; });
while (!q.empty()) {
std::cout << "Consumed: " << q.front() << std::endl;
q.pop();
}
if (done) break;
}
}
int main() {
std::thread p(producer);
std::thread c(consumer);
p.join();
c.join();
return 0;
}
八、Day 4 练习题
📝 基础练习
-
使用
std::condition_variable
实现:- 线程 A 等待线程 B 设定一个变量
flag = true
,收到通知后打印“Flag is set!”。
- 线程 A 等待线程 B 设定一个变量
-
修改生产者-消费者模型:
- 支持多个消费者线程(3个线程同时消费)。
🧠 进阶思考
- 如果不使用谓词版
wait(lock, predicate)
,可能出现什么问题? - 使用
cv.notify_all()
会不会导致资源被多个线程重复消费?如何防止?
九、小结
知识点 | 内容 |
---|---|
线程通信 | 条件变量用于线程间等待/通知机制 |
wait() |
阻塞线程直到条件满足 |
notify_one() |
唤醒一个等待线程 |
notify_all() |
唤醒所有等待线程 |
安全写法 | 使用谓词 lambda 防止虚假唤醒 |