为什么只有一个锁和一个原子计数器的条件变量会错误地唤醒?
Why does the condition variable wake up erroneously with only one lock and an atomic counter?
我正在调试一些线程代码,遇到了一些我不理解的行为。
我创建了一个线程向量。我有变量 atomic_uint
Counter
和 atomic_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
(除了主线程)。
Each thread thread waits on a condition that the counter is not zero
这实际上不是你的情况:
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()
)
我正在调试一些线程代码,遇到了一些我不理解的行为。
我创建了一个线程向量。我有变量 atomic_uint
Counter
和 atomic_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
(除了主线程)。
Each thread thread waits on a condition that the counter is not zero
这实际上不是你的情况:
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()
)