std::condition_variable
is a low level primitive. Part of its design is that spurious wakeups happen; this means, sometimes people waiting on notifications will get notified even though nobody sent one.
I suspect the reason is a few fold
The underlying mechanism on most OS's has such spurious wakeups. So the std library implementor would have to write the code to handle it if it hid it from the end user.
In almost every use case of std::condition_variable
, the code to handle threads moving faster/slower than expected ends up being connected to and overlap with efficient spurious wakeup code. So if the library handled it for you, you'd end up duplicating the work anyhow.
Your next problem is that the logic you have described is a bit vague. Time in a computer should not be treated as absolute; there is no "during the same 60s interval" in two different threads.
There is happens-before some synchronization and happens-after that synchronization.
I suspect you might want a latch. A latch is a synchronization primitive (but less primitive than condition variable). Think of a latch on a door or a gate. It starts off closed, and you can open it; but once you open it, there is no way to close it again.
Here, the latch being "open" means "worker thread, cease your endless toil".
struct latch {
void open_latch() {
auto l = lock();
open = true;
cv.notify_all();
}
void wait() const {
auto l = lock();
cv.wait(l, [&]{ return open; });
}
template<class Rep, class Period>
bool wait_for(const std::chrono::duration<Rep, Period>& duration) const {
auto l = lock();
return cv.wait_for(l, duration, [&]{ return open; });
}
template<class Clock, class Period>
bool wait_until(const std::chrono::time_point<Clock, Period>& when) const {
auto l = lock();
return cv.wait_until(l, when, [&]{ return open; });
}
private:
auto lock() const { return std::unique_lock<std::mutex>(m); }
mutable std::mutex m;
bool open = false;
std::condition_variable cv;
};
now your code looks like:
latch l;
Thread1:
void ThreadWork
{
while(!l.wait_for(60s))
{
Work();
}
return;
}
Thread2:
void RequestEnd()
{
l.open_latch();
}
(Code not tested, but this isn't my first rodeo).
There are a bunch of things this pattern handles, including the latch being opened before anyone waits on it.
I'd advise using wait_until
instead of wait_for
if you want X instances of work to occur after X minutes (note if the work takes more than 1 minute, the waiting will be reduce to near zero time). If you instead want a 1 minute "break" between doing work, use wait_for
.
Almost all uses of std::condition_variable
has this 3 part system; the mutex, the payload and the condition variable. The mutex should pretty much always guard the payload (even if atomic!) and only the payload. Sometimes the payload is two-part, like an abort flag and a more complex data structure.