Implementing boost::barrier in C++11
Solution 1
class Barrier {
public:
explicit Barrier(std::size_t iCount) :
mThreshold(iCount),
mCount(iCount),
mGeneration(0) {
}
void Wait() {
std::unique_lock<std::mutex> lLock{mMutex};
auto lGen = mGeneration;
if (!--mCount) {
mGeneration++;
mCount = mThreshold;
mCond.notify_all();
} else {
mCond.wait(lLock, [this, lGen] { return lGen != mGeneration; });
}
}
private:
std::mutex mMutex;
std::condition_variable mCond;
std::size_t mThreshold;
std::size_t mCount;
std::size_t mGeneration;
};
Solution 2
Use a std::condition_variable instead of a std::mutex to block all threads until the last one reaches the barrier.
class Barrier
{
private:
std::mutex _mutex;
std::condition_variable _cv;
std::size_t _count;
public:
explicit Barrier(std::size_t count) : _count(count) { }
void Wait()
{
std::unique_lock<std::mutex> lock(_mutex);
if (--_count == 0) {
_cv.notify_all();
} else {
_cv.wait(lock, [this] { return _count == 0; });
}
}
};
Solution 3
Here's my version of the accepted answer above with Auto reset behavior for repetitive use; this was achieved by counting up and down alternately.
/**
* @brief Represents a CPU thread barrier
* @note The barrier automatically resets after all threads are synced
*/
class Barrier
{
private:
std::mutex m_mutex;
std::condition_variable m_cv;
size_t m_count;
const size_t m_initial;
enum State : unsigned char {
Up, Down
};
State m_state;
public:
explicit Barrier(std::size_t count) : m_count{ count }, m_initial{ count }, m_state{ State::Down } { }
/// Blocks until all N threads reach here
void Sync()
{
std::unique_lock<std::mutex> lock{ m_mutex };
if (m_state == State::Down)
{
// Counting down the number of syncing threads
if (--m_count == 0) {
m_state = State::Up;
m_cv.notify_all();
}
else {
m_cv.wait(lock, [this] { return m_state == State::Up; });
}
}
else // (m_state == State::Up)
{
// Counting back up for Auto reset
if (++m_count == m_initial) {
m_state = State::Down;
m_cv.notify_all();
}
else {
m_cv.wait(lock, [this] { return m_state == State::Down; });
}
}
}
};
Related videos on Youtube
h4lc0n
Game Developer, also worked as a Software Engineer specialized in porting videogames to PSVita, PS3, PS4, Switch, XB1, XBS, MacOSX and other platforms, currently working at Larian Studios in Barcelona, Spain.
Updated on September 11, 2022Comments
-
h4lc0n over 1 year
I've been trying to get a project rid of every boost reference and switch to pure C++11.
At one point, thread workers are created which wait for a barrier to give the 'go' command, do the work (spread through the N threads) and synchronize when all of them finish. The basic idea is that the main loop gives the go order (boost::barrier .wait()) and waits for the result with the same function.
I had implemented in a different project a custom made Barrier based on the Boost version and everything worked perfectly. Implementation is as follows:
Barrier.h:
class Barrier { public: Barrier(unsigned int n); void Wait(void); private: std::mutex counterMutex; std::mutex waitMutex; unsigned int expectedN; unsigned int currentN; };
Barrier.cpp
Barrier::Barrier(unsigned int n) { expectedN = n; currentN = expectedN; } void Barrier::Wait(void) { counterMutex.lock(); // If we're the first thread, we want an extra lock at our disposal if (currentN == expectedN) { waitMutex.lock(); } // Decrease thread counter --currentN; if (currentN == 0) { currentN = expectedN; waitMutex.unlock(); currentN = expectedN; counterMutex.unlock(); } else { counterMutex.unlock(); waitMutex.lock(); waitMutex.unlock(); } }
This code has been used on iOS and Android's NDK without any problems, but when trying it on a Visual Studio 2013 project it seems only a thread which locked a mutex can unlock it (assertion: unlock of unowned mutex).
Is there any non-spinning (blocking, such as this one) version of barrier that I can use that works for C++11? I've only been able to find barriers which used busy-waiting which is something I would like to prevent (unless there is really no reason for it).
-
Nobody moving away from SE almost 9 yearsI like the fact that this works over several generations.
-
Levi Morrison about 8 yearsNote that this implementation is suitable only for a one-time use barrier but is safe from spurious wake-ups.
-
Levi Morrison about 8 yearsThis implementation is reusable and safe from spurious wake-ups.
-
thegreatcoder over 5 yearsSuppose I have three threads that need to wait on the barrier and I initialize the barrier as Barrier barrier(2); this code does not seem to work.
-
jamshid over 4 yearsI believe there's a typo, should be lLock(mMutex) not lLock{mMutex}.
-
Romeo Valentin over 4 years@jamshid No it's not a typo, have a look at the examples on
list initialization
-
murfel over 3 yearsIf you're curious why
std::unique_lock
rather thanstd::lock_guard
, it's because it's relockable. We need to release the lock when waiting on the conditional variable. See this SO answer -
murfel over 3 yearsSee also the original Boost implementation and API documentation. Also, this SO answer explains the need for
mGeneration
. -
murfel over 3 yearsIt's better to release the lock before calling
notify_all()
(that's what the Boost implementation does), see this SO answer.