C++ 使用c+编写(旋转)螺纹屏障+;11原子

C++ 使用c+编写(旋转)螺纹屏障+;11原子,c++,multithreading,gcc,c++11,C++,Multithreading,Gcc,C++11,我试图熟悉c++11原子,因此我尝试为线程编写一个barrier类(在有人抱怨没有使用现有类之前:这更多是为了学习/自我改进,而不是出于任何实际需要)。我的课程基本上如下所示: class barrier { private: std::atomic<int> counter[2]; std::atomic<int> lock[2]; std::atomic<int> cur_idx; int thread_count; pub

我试图熟悉c++11原子,因此我尝试为线程编写一个barrier类(在有人抱怨没有使用现有类之前:这更多是为了学习/自我改进,而不是出于任何实际需要)。我的课程基本上如下所示:

class barrier
{
private:
    std::atomic<int> counter[2];
    std::atomic<int> lock[2];
    std::atomic<int> cur_idx;
    int thread_count;
public:
    //constructors...
    bool wait();
};
int idx = cur_idx;
if(lock[idx] == 0)
{
    __sync_val_compare_and_swap(&lock[idx], 0, 1);
}
int val = __sync_fetch_and_add(&counter[idx], 1);
if(val >= thread_count - 1)
{
    __sync_synchronize();
    counter[idx] = 0;
    cur_idx ^= 1;
    __sync_synchronize();
    lock[idx] = 0;
    __sync_synchronize();
    return true;
}
while(lock[idx] == 1);
return false;
但是,当尝试将其用于两个线程(
thread\u count
为2)时,第一个线程进入等待循环很好,但第二个线程没有解锁障碍(似乎它甚至没有到达
int val=counter[idx]。fetch\u add(1);
,但我不太确定。但是,当我使用gcc原子内部函数时,使用
volatile int
而不是
std::atomic
并编写
wait
,如下所示:

class barrier
{
private:
    std::atomic<int> counter[2];
    std::atomic<int> lock[2];
    std::atomic<int> cur_idx;
    int thread_count;
public:
    //constructors...
    bool wait();
};
int idx = cur_idx;
if(lock[idx] == 0)
{
    __sync_val_compare_and_swap(&lock[idx], 0, 1);
}
int val = __sync_fetch_and_add(&counter[idx], 1);
if(val >= thread_count - 1)
{
    __sync_synchronize();
    counter[idx] = 0;
    cur_idx ^= 1;
    __sync_synchronize();
    lock[idx] = 0;
    __sync_synchronize();
    return true;
}
while(lock[idx] == 1);
return false;
就我的理解而言,这两个版本之间不应该有任何根本性的区别(更重要的是,第二个版本不太可能起作用)。那么以下哪种情况适用

  • 第二次实现我很幸运,我的算法很糟糕
  • 我没有完全理解std::atomic,第一个变量(但不是第二个)有问题
  • 它应该可以工作,但是c++11库的实验实现并不像我希望的那样成熟
  • 为了记录在案,我在GCC4.6.1中使用了32位mingw

    调用代码如下所示:

    spin_barrier b(2);
    std::thread t([&b]()->void
    {
        std::this_thread::sleep_for(std::chrono::duration<double>(0.1));
        b.wait();
    });
    b.wait();
    t.join();
    
    std::atomic_flag consumerLock;
    
    {
        // critical section
    
        while (consumerLock.test_and_set()) { /* spin */ }
    
        // do stuff
    
        consumerLock.clear();
    }
    
    spin_势垒b(2);
    标准::线程t([&b]()->无效
    {
    std::this_thread::sleep_for(std::chrono::duration(0.1));
    b、 等待();
    });
    b、 等待();
    t、 join();
    
    因为mingw没有whave
    头文件,所以我使用了一个自编版本,它基本上包装了适当的pthread函数(在有人问:是的,它可以在没有障碍的情况下工作,所以包装应该没有问题) 如有任何见解,将不胜感激

    编辑:对算法进行解释以使其更清晰:

    • thread\u count
      是等待屏障的线程数(因此,如果
      thread\u count
      线程在屏障中,所有线程都可以离开屏障)
    • 当第一条(或任何一条)螺纹进入屏障时,
      lock
      设置为1
    • 计数器
      统计屏障内的线程数,并为每个线程自动递增一次
    • 如果计数器>=线程计数
      所有线程都在屏障内,因此计数器和锁重置为零
    • 否则,线程将等待
      锁变为零
    • 在下次使用屏障时,将使用不同的变量(
      计数器
      ),确保线程仍在等待首次使用屏障时不会出现问题(例如,当屏障解除时,线程已被抢占)
    编辑2:
    现在,我已经在linux下使用GCC4.5.1对它进行了测试,这两个版本似乎都工作得很好,这似乎表明mingw的
    std::atomic
    存在一个问题,但我仍然不完全相信,因为查看
    标题发现,大多数函数只是调用适当的gcc原子,这意味着实际上不应该这样做bea两个版本之间的差异

    我不知道这是否会有所帮助,但以下来自Herb Sutter的并发队列实现的片段使用了基于原子的自旋锁:

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

    (如果愿意,您可以在那里使用获取和释放内存排序。)

    它看起来不必要的复杂。试试这个更简单的版本(好吧,我没有测试过,我只是想了想:):

    #包括
    类旋转障碍物
    {
    公众:
    自旋势垒(无符号整数n):n(n),nwait(0),步骤{0}
    bool wait()
    {
    unsigned int step=step.load();
    如果(nwait_uu.fetch_uadd(1)=n_uu-1)
    {
    /*好的,最后一个线程*/
    nwait_.store(0);//XXX:也许可以在这里使用轻松订购??
    步骤提取添加(1);
    返回true;
    }
    其他的
    {
    /*绕圈子跑,像小女孩一样尖叫*/
    while(步骤加载()==步骤)
    ;
    返回false;
    }
    }
    受保护的:
    /*同步线程的数量*/
    常量无符号整型n_2;;
    /*当前正在旋转的线程数*/
    std::原子nwait_;
    /*到目前为止完成的屏障同步数量,
    *包起来没问题*/
    std::原子阶跃;
    };
    
    编辑:
    @Grizzy,我在你的第一个(C++11)版本中找不到任何错误,我也用两个线程运行了大约一亿次同步,它完成了。不过,我已经在双插槽/四核GNU/Linux机器上运行过它,所以我倾向于怀疑您的选项3。-该库(或者更确切地说,它到win32的端口)不够成熟。

    这里是我的一个简单版本:

    // 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();
        }
    };
    
    //spinning_mutex.hpp
    #(包括示例)

    #包括
    #包括
    #包括“spinning_mutex.hpp”
    int g_i=0;
    旋转互斥体g_i_互斥体;//保护g_i
    无效安全增量()
    {
    std::锁定保护锁(g_i_互斥锁);
    ++g_i;
    //g_i_互斥锁在锁定时自动释放
    //超出范围
    }
    int main()
    {
    标准:螺纹t1(安全增量);
    标准:螺纹t2(安全增量);
    t1.join();
    t2.连接();
    }
    
    我知道线程有点旧了,但由于它仍然是仅使用c++11搜索线程屏障时的第一个google结果,因此我想提供一个解决方案,使用
    std::condition\u变量来摆脱繁忙的等待。
    基本上,它是chill的解决方案,但它使用的不是
    while
    循环,而是
    std::conditional\u变量。wait()
    std::conditional\u变量。notify\u all()
    。在我的测试中,它似乎运行良好

    #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;
    };
    
    #包括
    #包括
    #包括
    类自旋势垒
    {
    公众:
    SpinningBarrier(无符号整数线程计数):
    threadCnt(threadCo
    
    #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;
    };
    
    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);
            }
        }
    };
    
    #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
    
    #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; 
    };
    
    #include "spinlock.h"
    
    atomic_flag kartuliga = ATOMIC_FLAG_INIT;
    
    void mutually_exclusive_function()
    {
        spinlock lock(kartuliga);
        /* your shared-resource-using code here */
    }