You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于std::condition_variable notify_one的线程唤醒行为疑问

std::condition_variable边缘场景疑问分析

问题场景

测试std::condition_variable的边缘场景:设置99个生产者和1个消费者操作有最大容量的队列。队列满后调用notify_one时,命中消费者的概率仅约1%,大概率会唤醒另一个生产者,该生产者检查谓词后会继续等待。

原以为程序会在此处挂起,但实际观察到程序会持续检查谓词直到命中消费者;若没有消费者,则会永久遍历所有等待线程。

核心疑问

  • C++标准是否规定notify_one会尝试唤醒首个谓词结果为真的等待线程?
  • 为何注释掉谓词的消费者代码也能正常工作?
  • 队列满后出现的大量谓词检查是否由虚假唤醒导致?

测试环境

  • 操作系统:Windows
  • 编译器:Clang、MSVC

复现代码

CVSimple.h

#include <condition_variable>
#include <mutex>
#include <queue>
#include <vector>
#include <iostream>

class CVSimple
{

public:
    static void test() {
        std::queue<int> que;
        std::mutex m;
        std::condition_variable cv;
        int max_size = 10;
        bool working = true;


        auto producer = [&]() {
            while (working) {
                std::unique_lock<std::mutex> lock(m);
                std::chrono::milliseconds t(200);
                auto predicate = [&que, &max_size]() { 
                    if (que.size() < max_size) {
                        std::cout << "T";
                        return true;
                    } 
                    std::cout << "F";
                    return false;
                };
                if (cv.wait_for(lock, t, predicate)) {
                    std::cout << "+";
                    std::this_thread::sleep_for(std::chrono::milliseconds(50));
                    que.push(1);
                    lock.unlock();
                    std::cout << "N";
                    cv.notify_one();
                }
                else {
                    //std::cout << "P";
                }

            }
        };

        auto consumer = [&]() {
            while (working) {
                std::unique_lock<std::mutex> lock(m);
                std::chrono::milliseconds t(200);
                auto predicate = [&que]() {
                    if (!que.empty()) {
                        std::cout << "t";
                        return true;
                    }
                    else {
                        std::cout << "f";
                        return false;
                    };
                };
                //cv.wait(lock, predicate);
                //std::cout << "-";
                //std::this_thread::sleep_for(std::chrono::milliseconds(50));
                //que.pop();
                //lock.unlock();
                //std::cout << "n";
                //cv.notify_one();


                if (cv.wait_for(lock, t, predicate)) {
                    std::cout << "-";
                    std::this_thread::sleep_for(std::chrono::milliseconds(50));
                    que.pop();
                    lock.unlock();
                    std::cout << "n";
                    cv.notify_one();
                }
                else {
                    std::cout << "o";
                }
            }
        };

        int nprod = 100;
        int ncons = 1;

        std::cout << "Start producers" << std::endl;
        std::vector<std::thread> threads;
        for (int i = 0; i < nprod; ++i) {
            threads.emplace_back(producer);
        }

        std::cout << "Start consumers" << std::endl;
        for (int i = 0; i < ncons; ++i) {
            threads.emplace_back(consumer);
        }

        std::this_thread::sleep_for(std::chrono::seconds(20));
        std::cout << "Stop working" << std::endl;
        working = false;
        for (auto& th : threads) {
            if (th.joinable()) {
                th.join();
            }
        }
        return;
    }
};

main.cpp

#include "CVSimple.h"

int main()
{
    CVSimple::test();
}

输出示例

Start producers
T+Start consumers
NT+NT+NT+NT+NT+NT+NT+NT+NT+NFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFt-nT+NFFFFFFFFFFt-nT+NFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

解答

1. 关于notify_one的标准规定

C++标准没有任何规定要求notify_one优先唤醒谓词结果为真的线程。notify_one的行为是从当前等待该条件变量的线程集合中,选择一个线程进行唤醒——选择逻辑是实现定义的(可能是随机、FIFO或其他顺序),完全不考虑线程等待时绑定的谓词。

谓词的检查是线程被唤醒后,在持有锁的前提下自行执行的步骤,这也是为什么wait/wait_for必须搭配谓词的核心原因:防止线程被唤醒后,条件仍不满足导致错误的业务操作。

2. 无渭词的消费者代码“正常工作”的原因

这只是测试中的巧合,本质上是不安全的写法。cv.wait(lock)没有谓词时,线程可能被虚假唤醒(这是条件变量的固有特性,标准允许实现产生虚假唤醒),此时队列可能为空,执行que.pop()会触发未定义行为。

你当前测试中未出现问题,要么是虚假唤醒未发生,要么是消费者被唤醒时队列恰好非空,但这不是可靠的行为。正确的写法必须搭配谓词,确保只有当队列非空时才继续执行后续操作。

3. 队列满后大量谓词检查的原因

这不是虚假唤醒导致的,而是因为你使用了cv.wait_for(lock, t, predicate)。该函数的逻辑是:如果谓词初始不满足,线程会进入等待,但每隔t时间就会自动醒来,重新检查谓词——无论是否有notify_one调用。

当队列满时,生产者的谓词返回false,wait_for会等待200毫秒后唤醒线程,再次检查谓词并输出F,然后继续等待,循环往复,这就是你看到大量F输出的原因。此外,当一个生产者被notify_one唤醒但谓词不满足时,它会重新进入等待,但其他生产者的wait_for定时到期会不断醒来检查谓词,因此会持续看到F。

如果将wait_for替换为wait(lock, predicate),线程只会在被notify或虚假唤醒时才会醒来检查谓词,此时队列满后生产者会进入等待,直到消费者操作后唤醒它们。


内容的提问来源于stack exchange,提问作者Piwniczne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:40:53