Several weeks ago, while I was working on the next version of Horodruin, I was optimizing the multithreaded file copy engine.
This optimization phase pops up an unexpected event on the standard event object processing. It seems that frequent uses of SetEvent() API are not exactly processing-resources free.
This is probably caused by some code overhead needed for a system object…
During these evaluations, I started wondering if can do something to replace this object. The answer was a lightspeed simple event-like object.
The first needed property was to change its state using minimal processing resources.
The second property was the ability to wait X ms for the ‘signaled’ state. This last task can become tricky when we cannot use the usual WaitFor*Object() APIs.
The Implementation
My object uses one SRWLock and one Condition Variable.
class CSynEvent { private: SRWLOCK srwl_lock; // sync magic bool bState; CONDITION_VARIABLE cv; public: CSynEvent (); bool Acquire (unsigned ms = INFINITE); bool _State (); void _State_Set (bool fNewState); };
This object is very simple, with several ‘properties/limits’:
- every waiting thread is blocked until when the object state is set to true (ie signaled) by using the .Acquire() method.
- the object state changes only manually (ie no ‘auto reset‘ feature) by using ._State_Set() and ._State() methods.
- no support for interprocess object sharing.
- all system objects don’t need to be freed/closed.
The methods are implemented as below:
// constructor CSynEvent::CSynEvent () { this->bState = true; ::InitializeSRWLock (&this->srwl_lock); ::InitializeConditionVariable (&this->cv); } // wait for (timeouted) bool CSynEvent::Acquire (unsigned ms) { bool res; ::AcquireSRWLockShared (&this->srwl_lock); res = (this->bState || ::SleepConditionVariableSRW (&this->cv, &this->srwl_lock, ms, CONDITION_VARIABLE_LOCKMODE_SHARED)); ::ReleaseSRWLockShared (&this->srwl_lock); return bState; } // get event state bool CSynEvent::_State () { bool res; ::AcquireSRWLockShared (&this->srwl_lock); res = this->bState; ::ReleaseSRWLockShared (&this->srwl_lock); return res; } // set event state void CSynEvent::_State_Set (bool bNewState) { bool bUseTrigger; ::AcquireSRWLockExclusive (&this->srwl_lock); bUseTrigger = (bNewState != this->bState); this->bState = bNewState; ::ReleaseSRWLockExclusive (&this->srwl_lock); if (bUseTrigger && bNewState) ::WakeAllConditionVariable (&this->cv); }
The heart of the object is the .Acquire() method where every caller thread:
- it sets a shared lock to the SRWLock.
- checks if the object state and it exits if it is signaled.
- if not, it uses the Condition Variable to wait for the state change.
The other part is ._State_Set() method, where the caller thread:
- it exclusively locks SRWLock exclusively.
- it detects any object state change
- it updates the object state.
- unlock it
- if there is a state change, and the object is now signaled, then wake up all sleeping threads.
Conclusions
Even if this object has several limitations, it is very simple and performant using the latest synchronization objects.