I'm writing a latency sensitive app that in effect wants to wait on multiple condition variables at once. I've read before of several ways to get this functionality on Linux (apparently this is builtin on Windows), but none of them seem suitable for my app. The methods I know of are:
Have one thread wait on each of the condition variables you want to wait on, which when woken will signal a single condition variable which you wait on instead.
Cycling through multiple condition variables with a timed wait.
Writing dummy bytes to files or pipes instead, and polling on those.
#1 & #2 are unsuitable because they cause unnecessary sleeping. With #1, you have to wait for the dummy thread to wake up, then signal the real thread, then for the real thread to wake up, instead of the real thread just waking up to begin with -- the extra scheduler quantum spent on this actually matters for my app, and I'd prefer not to have to use a full fledged RTOS. #2 is even worse, you potentially spend N * timeout time asleep, or your timeout will be 0 in which case you never sleep (endlessly burning CPU and starving other threads is also bad).
For #3, pipes are problematic because if the thread being 'signaled' is busy or even crashes (I'm in fact dealing with separate process rather than threads -- the mutexes and conditions would be stored in shared memory), then the writing thread will be stuck because the pipe's buffer will be full, as will any other clients. Files are problematic because you'd be growing it endlessly the longer the app ran.
Is there a better way to do this? Curious for answers appropriate for Solaris as well.
Your #3 option (writing dummy bytes to files or pipes instead, and polling on those) has a better alternative on Linux: eventfd
.
Instead of a limited-size buffer (as in a pipe) or an infinitely-growing buffer (as in a file), with eventfd
you have an in-kernel unsigned 64-bit counter. An 8-byte write
adds a number to the counter; an 8-byte read
either zeroes the counter and returns its previous value (without EFD_SEMAPHORE
), or decrements the counter by 1 and returns 1 (with EFD_SEMAPHORE
). The file descriptor is considered readable to the polling functions (select
, poll
, epoll
) when the counter is nonzero.
Even if the counter is near the 64-bit limit, the write
will just fail with EAGAIN
if you made the file descriptor non-blocking. The same happens with read
when the counter is zero.