Event reader in this case could be connected to X11 socket, where frequency of events depends from a user actions (resizing window, typing, etc.) And if the GUI thread's event dispatcher is checking for events at regular intervals (e.g. due to some timer events in user application) we don't want to needlessly block event reader thread by acquiring lock on the shared event queue which we know is empty. We can simply check if anything has been queued by using the 'dataReady' atomic. This is also known as "Double-checked locking" pattern.
namespace {
std::mutex mutex;
std::atomic_bool dataReady(false);
std::atomic_bool done(false);
std::deque<int> events; // shared event queue, protected by mutex
}
void eventReaderThread()
{
static int eventId = 0;
std::chrono::milliseconds ms(100);
while (true) {
std::this_thread::sleep_for(ms);
mutex.lock();
eventId++; // populate event queue, e.g from pending messgaes on a socket
events.push_back(eventId);
dataReady.store(true, std::memory_order_release);
mutex.unlock();
if (eventId == 10) {
done.store(true, std::memory_order_release);
break;
}
}
}
void guiThread()
{
while (!done.load(std::memory_order_acquire)) {
if (dataReady.load(std::memory_order_acquire)) { // Double-checked locking pattern
mutex.lock();
std::cout << events.front() << std::endl;
events.pop_front();
// If consumer() is called again, and producer() has not added new events yet,
// we will see the value set via this memory_order_relaxed.
// If producer() has added new events, we will see that as well due to normal
// acquire->release.
// relaxed docs say: "guarantee atomicity and modification order consistency"
dataReady.store(false, std::memory_order_relaxed);
mutex.unlock();
}
}
}
int main()
{
std::thread producerThread(eventReaderThread);
std::thread consumerThread(guiThread);
producerThread.join();
consumerThread.join();
}