Categories
boost-thread c++ c++11 multithreading synchronization

C++0x has no semaphores? How to synchronize threads?

146

Is it true that C++0x will come without semaphores? There are already some questions on Stack Overflow regarding the use of semaphores. I use them (posix semaphores) all the time to let a thread wait for some event in another thread:

void thread0(...)
{
  doSomething0();

  event1.wait();

  ...
}

void thread1(...)
{
  doSomething1();

  event1.post();

  ...
}

If I would do that with a mutex:

void thread0(...)
{
  doSomething0();

  event1.lock(); event1.unlock();

  ...
}

void thread1(...)
{
  event1.lock();

  doSomethingth1();

  event1.unlock();

  ...
}

Problem: It’s ugly and it’s not guaranteed that thread1 locks the mutex first (Given that the same thread should lock and unlock a mutex, you also can’t lock event1 before thread0 and thread1 started).

So since boost doesn’t have semaphores either, what is the simplest way to achieve the above?

1

  • 1

    Maybe use the condition mutex and std::promise and std::future?

    – Yves

    Dec 13, 2016 at 8:22

209

You can easily build one from a mutex and a condition variable:

#include <mutex>
#include <condition_variable>

class semaphore {
    std::mutex mutex_;
    std::condition_variable condition_;
    unsigned long count_ = 0; // Initialized as locked.

public:
    void release() {
        std::lock_guard<decltype(mutex_)> lock(mutex_);
        ++count_;
        condition_.notify_one();
    }

    void acquire() {
        std::unique_lock<decltype(mutex_)> lock(mutex_);
        while(!count_) // Handle spurious wake-ups.
            condition_.wait(lock);
        --count_;
    }

    bool try_acquire() {
        std::lock_guard<decltype(mutex_)> lock(mutex_);
        if(count_) {
            --count_;
            return true;
        }
        return false;
    }
};

34

  • 105

    someone should submit a proposal to the standards commitee

    – user90843

    Jun 6, 2012 at 23:36


  • 9

    a comment here that puzzled me initially is the lock in wait, one might ask how can a thread can get past notify if the lock is held by wait? the somewhat poorly obscurely documented answer is that condition_variable.wait pulses the lock, allowing another thread to get past notify in an atomic fashion, at least that’s how I understand it

    – user90843

    Jun 14, 2012 at 6:53


  • 36

    It was deliberately excluded from Boost on the basis that a semaphore is too much rope for programmers to hang themselves with. Condition variables supposedly are more manageable. I see their point but feel a bit patronized. I assume that the same logic applies to C++11 — programmers are expected to write their programs in a way that “naturally” uses condvars or other approved synchronization techniques. Supply a semaphore would run against that regardless of whether it’s implemented on top of condvar or natively.

    Aug 31, 2012 at 15:31

  • 5

    Note – See en.wikipedia.org/wiki/Spurious_wakeup for the rationale behind the while(!count_) loop.

    Nov 16, 2012 at 7:49

  • 4

    @Maxim I’m sorry, I don’t think you’re right. sem_wait and sem_post only syscall on contention too (check sourceware.org/git/?p=glibc.git;a=blob;f=nptl/sem_wait.c ) so the code here ends up duplicating the libc implementation, with potentially bugs. If you intend portability on any system, it might be a solution, but if you only need Posix compatibility, use Posix semaphore.

    – xryl669

    Aug 5, 2014 at 15:58


113

Based on Maxim Yegorushkin’s answer, I tried to make the example in C++11 style.

#include <mutex>
#include <condition_variable>

class Semaphore {
public:
    Semaphore (int count_ = 0)
        : count(count_) {}

    inline void notify()
    {
        std::unique_lock<std::mutex> lock(mtx);
        count++;
        cv.notify_one();
    }

    inline void wait()
    {
        std::unique_lock<std::mutex> lock(mtx);

        while(count == 0){
            cv.wait(lock);
        }
        count--;
    }

private:
    std::mutex mtx;
    std::condition_variable cv;
    int count;
};

6

  • 36

    You can make wait() also a three-liner: cv.wait(lck, [this]() { return count > 0; });

    – Domi

    Dec 6, 2013 at 13:14

  • 2

    Adding another class in the spirit of lock_guard is helpful, too. In RAII fashion, the constructor, which takes the semaphore as a reference, calls the semaphore’s wait() call, and the destructor calls its notify() call. This prevents exceptions from failing to release the semaphore.

    Oct 24, 2014 at 16:58

  • isn’t there a dead-lock, if say N threads called wait() and count==0, then cv.notify_one(); is never called, since the mtx hasn’t released?

    – Marcello

    May 18, 2015 at 21:26

  • 1

    @Marcello The waiting threads don’t hold the lock. The whole point of condition variables is to provide an atomic “unlock and wait” operation.

    Oct 19, 2015 at 21:39

  • 5

    You should release lock before calling notify_one() to avoid immediately blocking the wakeup… see here: en.cppreference.com/w/cpp/thread/condition_variable/notify_all

    – kylefinn

    Dec 20, 2019 at 18:31

41

I decided to write the most robust/generic C++11 semaphore I could, in the style of the standard as much as I could (note using semaphore = ..., you normally would just use the name semaphore similar to normally using string not basic_string):

template <typename Mutex, typename CondVar>
class basic_semaphore {
public:
    using native_handle_type = typename CondVar::native_handle_type;

    explicit basic_semaphore(size_t count = 0);
    basic_semaphore(const basic_semaphore&) = delete;
    basic_semaphore(basic_semaphore&&) = delete;
    basic_semaphore& operator=(const basic_semaphore&) = delete;
    basic_semaphore& operator=(basic_semaphore&&) = delete;

    void notify();
    void wait();
    bool try_wait();
    template<class Rep, class Period>
    bool wait_for(const std::chrono::duration<Rep, Period>& d);
    template<class Clock, class Duration>
    bool wait_until(const std::chrono::time_point<Clock, Duration>& t);

    native_handle_type native_handle();

private:
    Mutex   mMutex;
    CondVar mCv;
    size_t  mCount;
};

using semaphore = basic_semaphore<std::mutex, std::condition_variable>;

template <typename Mutex, typename CondVar>
basic_semaphore<Mutex, CondVar>::basic_semaphore(size_t count)
    : mCount{count}
{}

template <typename Mutex, typename CondVar>
void basic_semaphore<Mutex, CondVar>::notify() {
    std::lock_guard<Mutex> lock{mMutex};
    ++mCount;
    mCv.notify_one();
}

template <typename Mutex, typename CondVar>
void basic_semaphore<Mutex, CondVar>::wait() {
    std::unique_lock<Mutex> lock{mMutex};
    mCv.wait(lock, [&]{ return mCount > 0; });
    --mCount;
}

template <typename Mutex, typename CondVar>
bool basic_semaphore<Mutex, CondVar>::try_wait() {
    std::lock_guard<Mutex> lock{mMutex};

    if (mCount > 0) {
        --mCount;
        return true;
    }

    return false;
}

template <typename Mutex, typename CondVar>
template<class Rep, class Period>
bool basic_semaphore<Mutex, CondVar>::wait_for(const std::chrono::duration<Rep, Period>& d) {
    std::unique_lock<Mutex> lock{mMutex};
    auto finished = mCv.wait_for(lock, d, [&]{ return mCount > 0; });

    if (finished)
        --mCount;

    return finished;
}

template <typename Mutex, typename CondVar>
template<class Clock, class Duration>
bool basic_semaphore<Mutex, CondVar>::wait_until(const std::chrono::time_point<Clock, Duration>& t) {
    std::unique_lock<Mutex> lock{mMutex};
    auto finished = mCv.wait_until(lock, t, [&]{ return mCount > 0; });

    if (finished)
        --mCount;

    return finished;
}

template <typename Mutex, typename CondVar>
typename basic_semaphore<Mutex, CondVar>::native_handle_type basic_semaphore<Mutex, CondVar>::native_handle() {
    return mCv.native_handle();
}

7

  • This works, with a minor edit. The wait_for and wait_until method calls with the predicate return a boolean value (not a `std::cv_status).

    – jdknight

    Mar 4, 2015 at 20:17

  • 1

    sorry to nit-pick so late in the game. std::size_t is unsigned so decrementing it below zero is UB, and it will always be >= 0. IMHO count should be an int.

    Nov 29, 2015 at 18:44

  • 3

    @RichardHodges there’s no way to decrement below zero so there’s no problem, and what would a negative count on a semaphore mean? That doesn’t even make sense IMO.

    – David

    Dec 1, 2015 at 15:39


  • 1

    @David What if a thread had to wait for others to initalize things? for instance, 1 reader thread to wait for 4 threads, I would call the semaphore constructor with -3 to make the reader thread wait untill all the other threads made a post. I guess there are other ways to do that, but isn’t it reasonable? I think it’s in fact the question the OP is asking but with more “thread1″s.

    – jmmut

    Jun 20, 2016 at 13:48

  • 4

    @RichardHodges to be very pedantic, decrementing an unsigned integer type below 0 is not UB.

    – jcai

    Jun 2, 2017 at 19:29