我正在尝试熟悉c ++ 11原子,所以我尝试为线程编写一个屏障类(在有人抱怨不使用现有类之前:这更多是为了学习/自我改进,而不是由于任何实际需要)。我的班级基本上看起来如下:
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();
};
所有成员都被初始化为零,除了thread_count,它保存适当的计数。 我已将wait函数实现为
int idx = cur_idx.load();
if(lock[idx].load() == 0)
{
lock[idx].store(1);
}
int val = counter[idx].fetch_add(1);
if(val >= thread_count - 1)
{
counter[idx].store(0);
cur_idx.fetch_xor(1);
lock[idx].store(0);
return true;
}
while(lock[idx].load() == 1);
return false;
然而,当尝试使用两个线程(thread_count
是2)时,第一个线程进入等待循环就好了,但第二个线程没有解锁障碍(似乎它甚至没有转到int val = counter[idx].fetch_add(1);
,但我对此不太确定。但是当我使用volatile int
代替std::atomic<int>
并将wait
写为{g}时使用gcc atomic-intrinsics如下:
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
并且第一个变体存在问题(但不是第二个)对于记录我使用32位mingw和gcc 4.6.1
调用代码如下所示:
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();
由于mingw没有<thread>
标题,我使用自编写的版本,基本上包含了适当的pthread函数(在某人问之前:是的,它没有屏障,所以它不应该是包装问题)
任何见解都将不胜感激。
编辑:解释算法以使其更清晰:
thread_count
是等待屏障的线程数(因此,如果thread_count
线程在屏障中,则所有线程都可以离开屏障)。lock
设置为1。counter
计算屏障内有多少个线程,并为每个线程以原子方式递增一次if counter>=thread_count
所有线程都在屏障内,因此计数器和锁定重置为零lock
变为零counter
,lock
),确保线程仍在等待第一次使用障碍时没有问题(例如,它们已被抢占)当屏障被抬起时) EDIT2:
我现在已经在linux下使用gcc 4.5.1对它进行了测试,其中两个版本似乎工作得很好,这似乎指向了mingw的std::atomic
的问题,但我仍然没有完全相信,因为我看到了<atomic>
标题重新表示大多数函数只是调用适当的gcc-atomic意味着两个版本之间确实不应该有区别
答案 0 :(得分:23)
我不知道这是否会有所帮助,但Herb Sutter实现并发队列的以下片段使用了基于原子的自旋锁:
std::atomic<bool> consumerLock;
{ // the critical section
while (consumerLock.exchange(true)) { } // this is the spinlock
// do something useful
consumerLock = false; // unlock
}
事实上,标准为这种结构提供了一种特制的类型,需要进行无锁操作std::atomic_flag
。有了这个,关键部分看起来像这样:
std::atomic_flag consumerLock;
{
// critical section
while (consumerLock.test_and_set()) { /* spin */ }
// do stuff
consumerLock.clear();
}
(如果您愿意,可以使用获取和释放内存排序。)
答案 1 :(得分:5)
看起来不必要复杂。尝试这个更简单的版本(好吧,我还没有测试过,我只是在它上面思考:))):
#include <atomic>
class spinning_barrier
{
public:
spinning_barrier (unsigned int n) : n_ (n), nwait_ (0), step_(0) {}
bool wait ()
{
unsigned int step = step_.load ();
if (nwait_.fetch_add (1) == n_ - 1)
{
/* OK, last thread to come. */
nwait_.store (0); // XXX: maybe can use relaxed ordering here ??
step_.fetch_add (1);
return true;
}
else
{
/* Run in circles and scream like a little girl. */
while (step_.load () == step)
;
return false;
}
}
protected:
/* Number of synchronized threads. */
const unsigned int n_;
/* Number of threads currently spinning. */
std::atomic<unsigned int> nwait_;
/* Number of barrier syncronizations completed so far,
* it's OK to wrap. */
std::atomic<unsigned int> step_;
};
修改强> @Grizzy,我在你的第一个(C ++ 11)版本中找不到任何错误,而且我也运行它,就像有两个线程的一亿个同步一样,它就完成了。我在双插槽/四核GNU / Linux机器上运行它,所以我更倾向于怀疑你的选择3. - 库(或者更确切地说,它的端口到win32)还不够成熟。 / p>
答案 2 :(得分:5)
以下是本书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);
}
}
};
答案 3 :(得分:4)
这是我的一个简单版本:
// 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();
}
};
用法:(来自std::lock_guard示例)
#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();
}
答案 4 :(得分:2)
我知道这个帖子有点旧,但是因为它仍然是第一个使用c ++ 11搜索线程障碍时的google结果,所以我想提出一个解决方案来摆脱繁忙的等待使用std::condition_variable
。
基本上它是寒冷的解决方案,但它使用while
和std::conditional_variable.wait()
而不是std::conditional_variable.notify_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;
};
答案 5 :(得分:2)
为什么不使用std :: atomic_flag(来自C ++ 11)?
http://en.cppreference.com/w/cpp/atomic/atomic_flag
std :: atomic_flag是一种原子布尔类型。与所有专业不同 std :: atomic,它保证是无锁的。
以下是我编写旋转线程障碍类的方法:
#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
答案 6 :(得分:1)
直接从文档中被盗
#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 */
}