Reputation: 181
I'm debugging some threading code, and I encountered some behavior that I don't understand.
I create a vector of threads. I have the variables atomic_uint
Counter
and an atomic_bool
Stop
, telling when the threads should stop. Each thread thread waits on a condition that the counter is not zero, and then decrements it.
In the main thread I increment Counter
, and call notify_one()
on the condition. The code is as follows.
#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;
}
In the threading code, I have an additional atomic, ThreadCounter
, to keep track of how many times Counter
was actually decremented.
ThreadCounter
always gets incremented many more times than Condition.notify_one()
gets called:
ThreadCounter = 212
MainCounter = 100
How does this happen when I am locking the condition with one lock? As far as I understand, only one thread at a time can access Counter
(in addition to the main thread).
Upvotes: 0
Views: 115
Reputation: 303097
Each thread thread waits on a condition that the counter is not zero
That's actually not your condition:
Condition.wait( lock, [&Counter, &Stop]() -> bool {
//wait while this is false
return Counter.load() >= 0u || Stop;
// ^^^^^^^^^^^^^^^^^^^^
} );
Counter
is unsigned, so >= 0u
is always true. If Counter == 0
then your loop body will increment ThreadCounter
potentially lots of times, hence your discrepancy.
You probably meant:
return Counter > 0 || Stop;
(You don't need to call .load()
there)
Upvotes: 3