The reason for this is that we can only operate on a single variable atomically, yet we have two values that must be tracked - the status of the event (set or cleared), and the number of waiters (if cleared). The compound value structure consists of the highest bit indicating that the event is set, and the low bits indicating the number of waiters. Logically, it's impossible for there to be any waiters when the event is set.
With this configuration, it should be fairly obvious how to implement this. The implementation will rely heavily on the fact that we can read a variable and then change it at a later time, as long as we can verify that the variable hasn't been changed in between (which we can, using AtomicCompareExchange - if the variable has been changed when we try to write it back, we simply read it again and repeat). This allows us to read the status value, perform logic on it, and then write it back with some computed new value, and still be totally thread-safe in the whole operation.
The Reset and TryWait methods are trivial - a single atomic function call - so I won't bother explaining them. Set and Wait aren't much more complicated, but I'll mention them a bit.
The only real logic that Set requires is what to do if the event is cleared. If it's a manual-reset event, all waiting threads get released, and the status becomes set. If it's an auto-reset event, what happens depends on whether there are waiting threads. If there are, then one thread is released, and the status remains cleared (although there's one less waiting thread). If no threads are waiting, the status becomes set, just like with the manual-reset event.
The logic in Wait is essentially the same. If the status isn't set, then the waiting thread count gets implemented, and the thread waits on the semaphore. If it's set and it's a manual-reset event, Wait returns immediately, and the status remains set. If the status is set and it's an auto-reset event, the status is cleared, and Wait returns (at this point no threads are waiting on the event).
The code for CFastEvent and the POSIX CEvent:
class CFastEvent { private: CSemaphore m_sem; // Semaphore that threads will wait on unsigned int m_nSpinCount; // Constant. The number of times to attempt to check if the event is set before waiting bool m_bAutoReset; // Constant int32 m_nStatus; // Atomically accessed. The sign bit indicates that the event is currently set. The rest of the bits are the number of waiting threads on the event when the event isn't set. // Various local constants enum { WAITER_MASK = 0x7FFFFFFFL, SET = 0x80000000L }; public: inline CFastEvent(bool bAutoReset, bool bSet, unsigned int nSpinCount = 0) : m_sem(0) { assert(nSpinCount >= 0); m_nSpinCount = nSpinCount; m_bAutoReset = bAutoReset; m_nStatus = (bSet ? SET : 0); } inline ~CFastEvent() { } void Set() { int32 nStatus, nNewStatus; do { nStatus = m_nStatus; if (nStatus == SET) return; // Already set if (nStatus && m_bAutoReset) nNewStatus = nStatus - 1; // If there are waiters and it's an auto-reset event, release 1 and leave unset else nNewStatus = SET; // Else set and release all } while (AtomicCompareExchange(&m_nStatus, nNewStatus, nStatus) != nStatus); // If there were any waiters, release the appropriate number if (nStatus) m_sem.Post(nStatus - (nNewStatus & WAITER_MASK)); } inline void Reset() { // If it's set, reset it; else do nothing AtomicCompareExchange(&m_nStatus, 0, SET); } inline bool TryWait() { // New event status if we successfully get the event int32 nNewStatus = (m_bAutoReset ? 0 : SET); // If it's previously set, we got it. Set or reset it based on whether it's an auto or manual-reset event. return (AtomicCompareExchange(&m_nStatus, nNewStatus, SET) == SET); } // Wait indefinitely for the event void Wait() { // Don't wait if we can get it by spinning unsigned int nSpinCount = m_nSpinCount; while (nSpinCount--) { if (TryWait()) return; } // Check if the event is set, queue as a waiter if not int32 nStatus, nNewStatus; do { nStatus = m_nStatus; if (nStatus == SET) nNewStatus = (m_bAutoReset ? 0 : SET); // Set or reset it else nNewStatus = nStatus + 1; // One more waiter } while (AtomicCompareExchange(&m_nStatus, nNewStatus, nStatus) != nStatus); if (nStatus == SET) return; // It was set // It wasn't set. Wait for it. m_sem.Wait(); } }; // Simply provide the CFastEvent class with an interface compatible with CEvent class CEvent : public CFastEvent { public: inline CFastEvent(bool bAutoReset, bool bSet) : CFastEvent(bAutoReset, bSet) { } }; |
No comments:
Post a Comment