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 练习题

📝 基础练习

  1. 使用 std::condition_variable 实现:

    • 线程 A 等待线程 B 设定一个变量 flag = true,收到通知后打印“Flag is set!”。
  2. 修改生产者-消费者模型:

    • 支持多个消费者线程(3个线程同时消费)。

🧠 进阶思考

  1. 如果不使用谓词版 wait(lock, predicate),可能出现什么问题?
  2. 使用 cv.notify_all() 会不会导致资源被多个线程重复消费?如何防止?

九、小结

知识点 内容
线程通信 条件变量用于线程间等待/通知机制
wait() 阻塞线程直到条件满足
notify_one() 唤醒一个等待线程
notify_all() 唤醒所有等待线程
安全写法 使用谓词 lambda 防止虚假唤醒

posted @ 2025-07-21 22:12  seekwhale13  阅读(17)  评论(0)    收藏  举报