Writing a (spinning) thread barrier using c++11 atomics

后端 未结 7 2141
花落未央
花落未央 2020-12-28 22:48

I\'m trying to familiarize myself with c++11 atomics, so I tried writing a barrier class for threads (before someone complains about not using existing classes: this is more

相关标签:
7条回答
  • 2020-12-28 23:35

    I know the thread is a little bit old, but since it is still the first google result when searching for a thread barrier using c++11 only, I want to present a solution that gets rid of the busy waiting using the std::condition_variable. Basically it is the solution of chill, but instead of the while loop it is using std::conditional_variable.wait() and std::conditional_variable.notify_all(). In my tests it seems to work fine.

    #include <atomic>
    #include <condition_variable>
    #include <mutex>
    
    
    class SpinningBarrier
    {
        public:
            SpinningBarrier (unsigned int threadCount) :
                threadCnt(threadCount),
                step(0),
                waitCnt(0)
            {}
    
            bool wait()
            {
                if(waitCnt.fetch_add(1) >= threadCnt - 1)
                {
                    std::lock_guard<std::mutex> lock(mutex);
                    step += 1;
    
                    condVar.notify_all();
                    waitCnt.store(0);
                    return true;
                }
                else
                {
                    std::unique_lock<std::mutex> lock(mutex);
                    unsigned char s = step;
    
                    condVar.wait(lock, [&]{return step == s;});
                    return false;
                }
            }
        private:
            const unsigned int threadCnt;
            unsigned char step;
    
            std::atomic<unsigned int> waitCnt;
            std::condition_variable condVar;
            std::mutex mutex;
    };
    
    0 讨论(0)
  • 2020-12-28 23:39

    Here is a simple version of mine :

    // spinning_mutex.hpp
    #include <atomic>
    
    
    class spinning_mutex
    {
    private:
        std::atomic<bool> lockVal;
    public:
        spinning_mutex() : lockVal(false) { };
    
        void lock()
        {
            while(lockVal.exchange(true) );
        } 
    
        void unlock()
        {
            lockVal.store(false);
        }
    
        bool is_locked()
        {
            return lockVal.load();
        }
    };
    

    Usage : (from std::lock_guard example)

    #include <thread>
    #include <mutex>
    #include "spinning_mutex.hpp"
    
    int g_i = 0;
    spinning_mutex g_i_mutex;  // protects g_i
    
    void safe_increment()
    {
        std::lock_guard<spinning_mutex> lock(g_i_mutex);
        ++g_i;
    
        // g_i_mutex is automatically released when lock
        // goes out of scope
    }
    
    int main()
    {
        std::thread t1(safe_increment);
        std::thread t2(safe_increment);
    
        t1.join();
        t2.join();
    }
    
    0 讨论(0)
  • 2020-12-28 23:40

    I have no idea if this is going to be of help, but the following snippet from Herb Sutter's implementation of a concurrent queue uses a spinlock based on atomics:

    std::atomic<bool> consumerLock;
    
    {   // the critical section
        while (consumerLock.exchange(true)) { }  // this is the spinlock
    
        // do something useful
    
        consumerLock = false;  // unlock
    }
    

    In fact, the Standard provides a purpose-built type for this construction that is required to have lock-free operations, std::atomic_flag. With that, the critical section would look like this:

    std::atomic_flag consumerLock;
    
    {
        // critical section
    
        while (consumerLock.test_and_set()) { /* spin */ }
    
        // do stuff
    
        consumerLock.clear();
    }
    

    (You can use acquire and release memory ordering there if you prefer.)

    0 讨论(0)
  • 2020-12-28 23:44

    Stolen straight from docs

    spinlock.h

    #include <atomic>
    
    using namespace std;
    
    /* Fast userspace spinlock */
    class spinlock {
    public:
        spinlock(std::atomic_flag& flag) : flag(flag) {
            while (flag.test_and_set(std::memory_order_acquire)) ;
        };
        ~spinlock() {
            flag.clear(std::memory_order_release);
        };
    private:
        std::atomic_flag& flag; 
    };
    

    usage.cpp

    #include "spinlock.h"
    
    atomic_flag kartuliga = ATOMIC_FLAG_INIT;
    
    void mutually_exclusive_function()
    {
        spinlock lock(kartuliga);
        /* your shared-resource-using code here */
    }
    
    0 讨论(0)
  • 2020-12-28 23:48

    Here is an elegant solution from the book C++ Concurrency in Action: Practical Multithreading.

    struct bar_t {
        unsigned const count;
        std::atomic<unsigned> spaces;
        std::atomic<unsigned> generation;
        bar_t(unsigned count_) :
            count(count_), spaces(count_), generation(0)
        {}
        void wait() {
            unsigned const my_generation = generation;
            if (!--spaces) {
                spaces = count;
                ++generation;
            } else {
                while(generation == my_generation);
            }
        }
    };
    
    0 讨论(0)
  • 2020-12-28 23:51

    Why not use std::atomic_flag (from C++11)?

    http://en.cppreference.com/w/cpp/atomic/atomic_flag

    std::atomic_flag is an atomic boolean type. Unlike all specializations of std::atomic, it is guaranteed to be lock-free.

    Here's how I would write my spinning thread barrier class:

    #ifndef SPINLOCK_H
    #define SPINLOCK_H
    
    #include <atomic>
    #include <thread>
    
    class SpinLock
    {
    public:
    
        inline SpinLock() :
            m_lock(ATOMIC_FLAG_INIT)
        {
        }
    
        inline SpinLock(const SpinLock &) :
            m_lock(ATOMIC_FLAG_INIT)
        {
        }
    
        inline SpinLock &operator=(const SpinLock &)
        {
            return *this;
        }
    
        inline void lock()
        {
            while (true)
            {
                for (int32_t i = 0; i < 10000; ++i)
                {
                    if (!m_lock.test_and_set(std::memory_order_acquire))
                    {
                        return;
                    }
                }
    
                std::this_thread::yield();  // A great idea that you don't see in many spinlock examples
            }
        }
    
        inline bool try_lock()
        {
            return !m_lock.test_and_set(std::memory_order_acquire);
        }
    
        inline void unlock()
        {
            m_lock.clear(std::memory_order_release);
        }
    
    private:
    
        std::atomic_flag m_lock;
    };
    
    #endif
    
    0 讨论(0)
提交回复
热议问题