No, not the type where you subscribe to an event using an interface or a callback. The type where you wait on an event to be signaled, just like Windows Event Objects.
Thanks to many suggestions from the kind people on Reddit’s r/cpp, I came up with 2 classes: manual_event
that can be waited on, and when signaled stays signaled until it’s reset, unblocking all waiting threads. And auto_event
that resets to non-signaled state when signaled, and unblocks only one waiting thread.
The below implementation is far from final and I am open to any suggestions from people experienced in multi-threaded programming.
The Usage Example
#include <iostream>
#include <thread>
#include <mutex>
#include "event.h"
using namespace std;
mutex cout_lock;
#define trace(x) { scoped_lock<mutex> lock(cout_lock); cout << x << endl; }
const int COUNT = 3;
void manual()
{
manual_event e;
for(int i = 0; i < COUNT; ++i)
thread([&](){
trace("manual " << this_thread::get_id() << " blocked");
e.wait();
trace("manual " << this_thread::get_id() << " unblocked");
}).detach();
this_thread::sleep_for(500ms);
e.signal();
this_thread::sleep_for(500ms);
e.reset();
for(int i = 0; i < COUNT; ++i)
thread([&](){
trace("manual " << this_thread::get_id() << " blocked");
e.wait();
trace("manual " << this_thread::get_id() << " unblocked");
}).detach();
this_thread::sleep_for(500ms);
e.signal();
this_thread::sleep_for(500ms);
}
void automatic()
{
auto_event e;
for(int i = 0; i < COUNT; ++i)
thread([&](){
trace("auto " << this_thread::get_id() << " blocked");
e.wait();
trace("auto " << this_thread::get_id() << " unblocked");
}).detach();
for(int i = 0; i < COUNT; ++i)
{
this_thread::sleep_for(500ms);
e.signal();
}
this_thread::sleep_for(500ms);
}
int main(int argc, char** argv)
{
manual();
automatic();
return 1;
}
The event.h
#pragma once
#include <mutex>
#include <condition_variable>
class manual_event
{
public:
explicit manual_event(bool signaled = false) noexcept
: m_signaled(signaled) {}
void signal() noexcept
{
std::unique_lock<std::mutex> lock(m_mutex);
m_signaled = true;
m_cv.notify_all();
}
void wait() noexcept
{
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [&](){ return m_signaled != false; });
}
void reset() noexcept
{
std::unique_lock<std::mutex> lock(m_mutex);
m_signaled = false;
}
private:
bool m_signaled = false;
std::mutex m_mutex;
std::condition_variable m_cv;
};
class auto_event
{
public:
explicit auto_event(bool signaled = false) noexcept
: m_signaled(signaled) {}
void signal() noexcept
{
std::unique_lock<std::mutex> lock(m_mutex);
m_signaled = true;
m_cv.notify_one();
}
void wait() noexcept
{
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [&](){ return m_signaled != false; });
m_signaled = false;
}
private:
bool m_signaled = false;
std::mutex m_mutex;
std::condition_variable m_cv;
};