Semaphores and Events for C++11

The C++11 standard finally introduced mechanisms for platform independent multithreading and thread synchronisation. Unfortunately, the new language standard lacks a popular synchronisation primitive called semaphore (see The Little Book of Semaphores for an introduction). Fortunately, the built-in mutexes and condition_variables can be used to build fully-fledged semaphore objects:


#include <mutex>
#include <condition_variable>
#include <chrono>

class Semaphore
{
private:
    unsigned int m_uiCount;
    std::mutex m_mutex;
    std::condition_variable m_condition;

public:
    inline Semaphore(unsigned int uiCount)
       : m_uiCount(uiCount) { }

    inline void Wait()
    {
        std::unique_lock< std::mutex > lock(m_mutex);
        m_condition.wait(lock,[&]()->bool{ return m_uiCount>0; });
        --m_uiCount;
    }

    template< typename R,typename P >
    bool Wait(const std::chrono::duration<R,P>& crRelTime)
    {
        std::unique_lock< std::mutex > lock(m_mutex);
        if (!m_condition.wait_for(lock,crRelTime,[&]()->bool{ return m_uiCount>0; }))
            return false;
        --m_uiCount;
        return true;
    }

    inline void Signal()
    {
        std::unique_lock< std::mutex > lock(m_mutex);
        ++m_uiCount;
        m_condition.notify_one();
    }
};

Method Wait() decrements the counter of the semaphore. As long as the counter value is positive (>0) a call to Wait() has no effect on the execution. Any call to Wait() with a counter value of zero will suspend the calling thread. Each subsequent call of Signal() increments the counter of the semaphore by one and wakes exactly one of the waiting threads (if any) in the order of their arrival (FIFO order).

Binary semaphores, sometimes called events, are an important variant of classical semaphores. Because the counter of a binary semaphore takes the maximum value of one, it is replaced by a boolean flag - which denotes whether the event is set or unset. Furthermore, a call to Wait() does not decrement the semaphore. Method Reset() sets the flag to false, corresponding to a counter value 0. Unlinke the classical semaphore a call to method Signal() sets the flag to true and wakes all waiting threads at once.


class Event
{
private:
    bool m_bFlag;
    mutable std::mutex m_mutex;
    mutable std::condition_variable m_condition;

public:
    inline Event() : m_bFlag(false) { }

    inline void Wait() const
    {
        std::unique_lock< std::mutex > lock(m_mutex);
        m_condition.wait(lock,[&]()->bool{ return m_bFlag; });
    }

    template< typename R,typename P >
    bool Wait(const std::chrono::duration<R,P>& crRelTime) const
    {
        std::unique_lock lock(m_mutex);
        if (!m_condition.wait_for(lock,crRelTime,[&]()->bool{ return m_bFlag; }))
            return false;
        return true;
    }

    inline bool Signal()
    {
        bool bWasSignalled;
        m_mutex.lock();
        bWasSignalled = m_bFlag;
        m_bFlag = true;
        m_mutex.unlock();
        m_condition.notify_all();
        return bWasSignalled == false;
    }
	
    inline bool Reset()
    {
        bool bWasSignalled;
        m_mutex.lock();
        bWasSignalled = m_bFlag;
        m_bFlag = false;
        m_mutex.unlock();
        return bWasSignalled;
    }

    inline bool IsSet() const { return m_bFlag; }
};

Both classes feature overloaded Wait() methods taking a timeout argument. These methods return false if the specified waiting time was exceeded. On the one hand this is useful for preventing infinite waiting. On the other hand it is possible to pass std::chrono::duration::zero(), i.e. a zero timeout, in order to check whether a call to Wait() would block the calling thread (this turns out to be useful when, e.g., filtering the queue of a producer/consumer system).

Because the used condition variables suffer from the Spurious Wakeup Problem the calls to methods std::condition_variable::wait() and std::condition_variable::wait_for() both specify optional waiting predicates in form of lambda expressions. These lambda expressions are used to check whether waiting should continue (i.e. a spurious wakeup occured) or the wakeup is intended.