folly::LifoSem

LifoSem is a semaphore that wakes its waiters in a manner intended to maximize performance rather than fairness. It should be preferred to a mutex+condvar or POSIX sem_t solution when all of the waiters are equivalent. It is faster than a condvar or sem_t, and it has a shutdown state that might save you a lot of complexity when it comes time to shut down your work pipelines. LifoSem is larger than sem_t, but that is only because it uses padding and alignment to avoid false sharing.

Synopsis

Declared in <folly/synchronization/LifoSem.h>

using LifoSem = LifoSemImpl<>;

Description

LifoSem allows multi-post and multi-tryWait, and provides a shutdown state that awakens all waiters. LifoSem is faster than sem_t because it performs exact wakeups, so it often requires fewer system calls. It provides all of the functionality of sem_t except for timed waiting. It is called LifoSem because its wakeup policy is approximately LIFO, rather than the usual FIFO.

The core semaphore operations provided are:

-- post() -- if there is a pending waiter, wake it up, otherwise increment the value of the semaphore. If the value of the semaphore is already 2^32-1, does nothing. Compare to sem_post().

-- post(n) -- equivalent to n calls to post(), but much more efficient. sem_t has no equivalent to this method.

-- bool tryWait() -- if the semaphore's value is positive, decrements it and returns true, otherwise returns false. Compare to sem_trywait().

-- uint32_t tryWait(uint32_t n) -- attempts to decrement the semaphore's value by n, returning the amount by which it actually was decremented (a value from 0 to n inclusive). Not atomic. Equivalent to n calls to tryWait(). sem_t has no equivalent to this method.

-- wait() -- waits until tryWait() can succeed. Compare to sem_wait().

-- timed wait variants - will wait until timeout. Note when these timeout, the current implementation takes a lock, blocking concurrent pushes and pops. (If timed wait calls are substantial, consider re-working this code to be lock-free).

LifoSem also has the notion of a shutdown state, in which any calls that would block (or are already blocked) throw ShutdownSemError. Note the difference between a call to wait() and a call to wait() that might block. In the former case tryWait() would succeed, and no isShutdown() check is performed. In the latter case an exception is thrown. This behavior allows a LifoSem controlling work distribution to drain. If you want to immediately stop all waiting on shutdown, you can just check isShutdown() yourself (preferrably wrapped in an UNLIKELY). This fast-stop behavior is easy to add, but difficult to remove if you want the draining behavior, which is why we have chosen the former.

All LifoSem operations except valueGuess() are guaranteed to be linearizable.