面试题:如何解决 C++ 中条件变量的信号丢失和虚假唤醒问题?

在 C++ 中,条件变量(std::condition_variable)是多线程编程中常用的同步机制,用于线程间的通信。然而,使用条件变量时可能会遇到 信号丢失 和 虚假唤醒 的问题。以下是这些问题的原因以及解决方法。


1. 信号丢失问题

问题描述:

当一个线程在调用 condition_variable::wait() 之前,另一个线程已经调用了 condition_variable::notify_one() 或 notify_all(),那么等待的线程可能会错过这个信号,导致它一直阻塞。

解决方法:

  • 使用谓词(Predicate)
    在调用 wait() 时,传入一个谓词(通常是一个 lambda 表达式或函数),用于检查条件是否满足。wait() 会在唤醒后自动检查谓词,如果条件不满足,它会继续等待。
    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;
    
    void waitForReady() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return ready; }); // 使用谓词避免信号丢失
        // 继续执行
    }
    
    void setReady() {
        std::unique_lock<std::mutex> lock(mtx);
        ready = true;
        cv.notify_one(); // 发送信号
    }

2. 虚假唤醒问题

问题描述:

即使没有线程调用 notify_one() 或 notify_all(),等待的线程也可能被唤醒。这种现象称为 虚假唤醒(Spurious Wakeup)。虚假唤醒是操作系统或底层实现的行为,无法完全避免。

解决方法:

  • 使用循环检查条件
    在 wait() 返回后,使用一个循环重新检查条件是否满足。如果条件不满足,继续调用 wait()
  • 使用带谓词的 wait()
    如前所述,wait() 的带谓词版本已经内置了循环检查机制,因此直接使用带谓词的 wait() 是最简洁的解决方案。

3. 完整示例

以下是一个完整的示例,展示了如何避免信号丢失和虚假唤醒:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void workerThread() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; }); // 使用谓词避免信号丢失和虚假唤醒
    std::cout << "Worker thread is processing data." << std::endl;
}

void mainThread() {
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟一些工作
    {
        std::unique_lock<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one(); // 通知等待的线程
}

int main() {
    std::thread worker(workerThread);
    std::thread main(mainThread);

    worker.join();
    main.join();

    return 0;
}

4. 总结

  • 信号丢失:通过使用带谓词的 wait() 避免信号丢失。
  • 虚假唤醒:通过循环检查条件或使用带谓词的 wait() 避免虚假唤醒。
  • 最佳实践:始终使用带谓词的 wait(),它是解决信号丢失和虚假唤醒问题的最简洁和可靠的方式。

通过正确使用条件变量和互斥锁,可以确保多线程程序的正确性和可靠性。

THE END
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容