I am using conditional variables to notify an unknown number of threads periodically. i.e. a loop will periodically modify a value and then notify all threads waiting on the variable. The usual method to avoid false wake-ups is to use a boolean variable.
The thread waiting on the variable after waking up, sets the value of the bool
to false
class foo
{
std::mutex mtx;
std::condition_variable cv;
bool update;
public:
void loop()
{
std::this_thread::seep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(mtx);
update = true;
}
cv.notify_all();
}
void wait()
{
while(!update)
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait();
if(update)
{
update = false;
break;
}
}
}
};
The function foo::loop
is started in a thread, and other threads that do periodic procedures will wait via the function foo::wait
.
But since the different threads may start waiting at different times, it would seem that each thread would require a dedicated boolean variable to ensure no false wake-ups.
Since I don't know how many threads may wait on it, i cant use arrays of bool or anything.
So is there any method other than using a std::map<std::thread::id,bool>
for tracking all updates.
By using a cycle counter, you can make sure that all threads wake up on the notify, and there are no false wake-ups. The only issue is the possibility of the cycle counter overflowing.
Note: I also moved the cv.wait()
predicate into a lambda.
class foo
{
std::mutex mtx;
std::condition_variable cv;
int cycle_count = 0;
public:
void loop()
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(mtx);
++cycle_count;
}
cv.notify_all();
}
void wait()
{
std::unique_lock<std::mutex> lock(mtx);
int expected_cycle = cycle_count + 1;
cv.wait(lock, [expected_cycle](){ return cycle_count >= expected_cycle; });
}
};
If it's okay that the controller thread waits for all workers before initiating an update, the cycle counter can be exchanged with a bool that's flipped back and forth, thus avoiding the overflow issue.
class foo
{
std::mutex mtx;
std::condition_variable cv;
bool is_cycle_count_even = true;
int threads_waiting;
int threads_to_notify;
public:
void loop()
{
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(mtx);
if (threads_to_notify == 0) { break; }
}
{
std::lock_guard<std::mutex> lock(mtx);
threads_to_notify = threads_waiting;
threads_waiting = 0;
is_cycle_count_even ^= true;
}
cv.notify_all();
}
void wait()
{
std::unique_lock<std::mutex> lock(mtx);
++threads_waiting;
bool expected_cycle = !is_cycle_count_even;
cv.wait(lock, [expected_cycle](){ return is_cycle_count_even == expected_cycle; });
--threads_to_notify;
}
};
This way, worker threads that enter wait()
after the controller has already started notifying (or at least acquired the mutex before the notify) will only get notified in the next cycle.