What's cool about conditions, though, is that they are linked to a variable in a mutex. To check if the condition is true, you'd do the following:
1. Enter the mutex
2. Check the value of the variable to see that the condition isn't already true
3. Wait on the condition to be signalled
4. Verify that the variable is now set so that the condition is true
5. Do whatever you want when the condition is true
6. Unlock the mutex
If you're a Windows programmer and you're used to using a separate mutex and event for this kind of thing, you'll notice a conspicuous absence: the mutex is not left before waiting on the condition, nor is it entered after waiting. You'd quickly learn of the error of your ways if you tried this with an event and mutex, as you'd deadlock the thread. The mutex doesn't remain locked while the thread is waiting on the condition, but it relocked when the thread comes out of wait.
Anyway, I'm getting sidetracked. So, here's the game plan (not that it's anything remarkable): a bool variable protected by a mutex, and a condition for threads to wait on. Threads waiting on the event will first check the bool, and if not set, wait on the condition. Threads setting the event will check if the event is false, and if so, set it and signal as many threads are appropriate (1 for auto-reset events, all for manual-reset events). As an optimization to prevent the signal if no threads are waiting, we tac on a waiting threads counter. And of course some crap to convert the relative time to wake to the absolute time to wake. The code:
class CEvent // POSIX version of event: condition variable (fast?) { private: bool m_bAutoReset; // Constant pthread_mutex_t m_mutex; // Mutex pthread_cond_t m_cond; // Condition variable // Protected by m_mutex bool m_bSet; // Set or clear unsigned int m_nWaitingThreads; // Number of threads waiting on the event // As the name implies, this must be called inside the mutex // Does the wait. The parameter specifies when the thread should wake up, should the event not get set before then. If this is NULL, the thread will wait indefinitely on the event. Returns whether the event got set (if not, the timeout must have expired). bool InnerWait(const timespec *restrict abstime) { if (!m_bSet) { m_nWaitingThreads++; do { int nRetVal; // Do the wait, either timed or indefinite if (abstime) nRetVal = pthread_cond_timedwait(&m_cond, &m_mutex, abstime); else nRetVal = pthread_cond_wait(&m_cond, &m_mutex); assert(nRetVal == 0 || nRetVal == ETIMEDOUT); } while (!m_bSet && nRetVal != ETIMEDOUT); // Loop until it gets set or the timeout expires m_nWaitingThreads--; } // Did the event get set? bool bSuccess = m_bSet; // If the event is set and it's an auto-reset event, reset it now that we're awake if (m_bSet && m_bAutoReset) m_bSet = false; return bSuccess; } public: CEvent(bool bAutoReset, bool bSet) { if (pthread_mutex_init(&m_mutex, NULL) != 0) throw std::bad_alloc("Unable to create mutex"); else if (pthread_cond_init(&m_cond, NULL) != 0) { pthread_mutex_destroy(&m_mutex); throw std::bad_alloc("Unable to create condition variable"); } m_bAutoReset = bAutoReset; m_bSet = bSet; m_nWaitingThreads = 0; } inline ~CEvent() { pthread_cond_destroy(&m_cond); pthread_mutex_destroy(&m_mutex); } void Set() { pthread_mutex_lock(&m_mutex); if (!m_bSet) // If it's already set, do nothing { m_bSet = true; // Set the event // Check if there are any waiters, and release them appropriately if (m_nWaitingThreads) { if (m_bAutoReset) pthread_cond_signal(&m_cond); // Release one thread else pthread_cond_broadcast(&m_cond); // Release all threads } } pthread_mutex_unlock(&m_mutex); } inline void Reset() { pthread_mutex_lock(&m_mutex); m_bSet = false; // Ding pthread_mutex_unlock(&m_mutex); } bool Wait(unsigned int nTimeoutMS) { // Calculate the time to wake based on the time to sleep. I hope I understand how this is supposed to work on POSIX. timespec now, timeout, later; clock_gettime(CLOCK_REALTIME, &now); timeout.tv_sec = nTimeoutMS / 1000; // Seconds timeout.tv_nsec = (nTimeoutMS % 1000) * 1000000L; // Nanoseconds later.tv_sec = now.tv_sec + timeout.tv_sec; later.tv_nsec = now.tv_nsec + timeout.tv_nsec; if (later.tv_nsec >= 1000000000L) { later.tv_nsec -= 1000000000L; later.tv_sec++; } pthread_mutex_lock(&m_mutex); bool bSuccess = InnerWait(&later); pthread_mutex_unlock(&m_mutex); return bSuccess; } inline void Wait() { pthread_mutex_lock(&m_mutex); InnerWait(NULL); pthread_mutex_unlock(&m_mutex); } }; |
1 comment:
Awesome, thanks for this!
I'm about to use it in a google NaCl application, thank you very much!
A code sample of how to use the class would be a cherry on top.
Post a Comment