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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#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; }; |
4 Replies to “Event objects”