为什么只有一个锁和一个原子计数器的条件变量会错误地唤醒

Why does the condition variable wake up erroneously with only one lock and an atomic counter?

本文关键字:条件 计数器 变量 唤醒 错误 一个 有一个 为什么      更新时间:2023-10-16

我正在调试一些线程代码,遇到了一些我不理解的行为。

我创建了一个线程向量。我有变量atomic_uint Counteratomic_bool Stop,告诉线程何时应该停止。每个线程线程在计数器不为零的条件下等待,然后递减

在主线程中,我递增Counter,并在此条件下调用notify_one()。代码如下。

#include <thread>
#include <condition_variable>
#include <atomic>
#include <vector>
#include <iostream>
#include <cstdlib>
#include <mutex>
int main() {
    const std::size_t       Tasks = 100u;
    const std::size_t       Repetitions = 100u;
    const std::size_t       Threads = 4u;
    std::mutex              Mutex;
    std::condition_variable Condition;
    std::atomic_uint        Counter(0);
    std::atomic_uint        MainCounter(0);
    std::atomic_uint        ThreadCounter(0);
    std::atomic_bool        Stop( false );

    std::vector<std::thread> v;
    for ( std::size_t i = 0; i < Threads; i++ ) {
        v.emplace_back( [&ThreadCounter,&Mutex, &Condition, &Counter, &Stop]() -> void {
            while ( true ) {
                {
                    std::unique_lock<std::mutex> lock( Mutex );
                    Condition.wait( lock, [&Counter, &Stop]() -> bool {
                        //wait while this is false
                        return Counter.load() >= 0u || Stop;
                    } );
                    if ( Stop && Counter == 0u ) {
                        return;
                    }
                    ThreadCounter++;
                    if ( Counter == 0 ) {
                        continue;
                    }
                    Counter--;
                }
            }   //while
        });
    }   //for
    for ( std::size_t i = 0u; i < Tasks; i++ ) {
        MainCounter++;
        Counter++;
        Condition.notify_one();
    }
    while ( Counter != 0u ) {
        std::this_thread::yield();
    }
    Stop = true;
    Condition.notify_all();
    for ( auto& t: v ) {
        t.join();
    }
    std::cout << "ThreadCounter = " << ThreadCounter.load() << std::endl;
    std::cout << "MainCounter = " << MainCounter.load() << std::endl;    
    return 0;
}

在线程代码中,我有一个额外的原子ThreadCounter,用来跟踪Counter实际减少了多少次。

ThreadCounter的增量总是比调用Condition.notify_one()的次数多得多:

ThreadCounter = 212
MainCounter = 100

当我用一把锁锁定条件时,这是如何发生的?据我所知,一次只有一个线程可以访问Counter(除了主线程)。

每个线程线程在计数器不是零的条件下等待

这实际上不是你的情况:

Condition.wait( lock, [&Counter, &Stop]() -> bool {
    //wait while this is false
    return Counter.load() >= 0u || Stop;
        // ^^^^^^^^^^^^^^^^^^^^
} );

Counter是无符号的,所以>= 0u总是真的。如果是Counter == 0,那么循环体可能会多次增加ThreadCounter,因此会出现差异。

你的意思可能是:

return Counter > 0 || Stop;

(您不需要在那里调用.load()