我一遍又一遍地阅读了boost和std(c ++ 11)原子类型和操作,但我仍然不确定我是否理解它(在某些情况下我根本不理解它)。所以,我有几个问题。
我用来学习的资料来源:
请考虑以下代码段:
atomic<bool> x,y;
void write_x_then_y()
{
x.store(true, memory_order_relaxed);
y.store(true, memory_order_release);
}
#1:它与下一个相同吗?
atomic<bool> x,y;
void write_x_then_y()
{
x.store(true, memory_order_relaxed);
atomic_thread_fence(memory_order_release); // *1
y.store(true, memory_order_relaxed); // *2
}
#2:以下陈述是真的吗?
Line * 1确保当在此行下完成的操作(例如* 2)可见时(对于使用acquire的其他线程),* 1以上的代码也将可见(使用新值)。
下一个剪辑扩展了以上内容:
void read_y_then_x()
{
if(y.load(memory_order_acquire))
{
assert(x.load(memory_order_relaxed));
}
}
#3:它与下一个相同吗?
void read_y_then_x()
{
atomic_thread_fence(memory_order_acquire); // *3
if(y.load(memory_order_relaxed)) // *4
{
assert(x.load(memory_order_relaxed)); // *5
}
}
#4:以下陈述是否属实?
#5:向原子整数递增(加1的操作)可以是memory_order_relaxed,并且不会丢失数据。唯一的问题是结果可见性的顺序和时间。
根据提升,以下剪辑是工作参考计数器:
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
#6为什么减少使用的memory_order_release?它是如何工作的(在上下文中)?如果我之前写的内容是真的,那么返回值的最新值是什么,特别是当我们使用后读取时而不是之前/期间?
#7为什么在参考计数器达到零后有获取订单?我们刚刚读到计数器为零,并且没有使用其他原子变量(指针本身没有标记/使用)。
答案 0 :(得分:1)
1:否。释放围栏与所有获取操作和围栏同步。如果在第三个线程中有第三个atomic<bool> z
被操纵,那么栅栏也会与第三个线程同步,这是不必要的。话虽如此,他们将在x86上采取相同的行动,但那是因为x86具有非常强的同步性。 1000核心系统使用的体系结构往往较弱。
2:是的,这是正确的。栅栏确保如果您看到后面的任何内容,您还会看到之前的所有内容。
3:一般来说它们是不同的,但实际上它们是相同的。允许编译器对不同变量上的两个松弛操作重新排序,但可能不会引入虚假操作。如果编译器有任何方式确信它需要读取x,那么在读取y之前可能会这样做。在您的特定情况下,这对于编译器来说非常困难,但是在许多类似的情况下,这种重新排序是公平的游戏。
4:所有这些都是真的。原子操作保证了一致性。他们并不总能保证事情按照你想要的顺序发生,他们只是防止破坏你的算法的病态命令。
5:正确。轻松的操作确实是原子的。他们只是不同步任何额外的内存
6:对于任何给定的原子对象M
,C ++保证在M
上有一个“官方”命令。你没有看到M
的“最新”值,就像C ++和处理器一样,所有线程都会看到M
的一系列一致值。如果两个线程递增引用计数,然后递减它,则没有保证将其减少为0,但是有一个保证,其中一个将看到它将它递减为0.它们都没有办法看到他们减少了2&gt; 1和2-> 1,但不知何故,refcount将它们组合为0.一个线程将始终看到2-> 1而另一个将看到1-> 0。
请记住,内存顺序更多的是围绕原子同步内存。无论你使用什么内存顺序,都可以正确处理原子。
7:这个比较棘手。 7的简短版本是递减是释放顺序,因为某些线程将必须运行x的析构函数,并且我们希望确保它在所有线程上看到x上的所有操作。在析构函数上使用释放顺序可满足此需求,因为您可以证明它是有效的。负责删除x的人在此之前获取所有更改(使用栅栏确保删除器中的原子不会向上漂移)。在线程释放自己的引用的所有情况下,显然所有线程在调用删除器之前将具有释放顺序减少。如果一个线程递增引用计数而另一个线程递减它,则可以证明唯一有效的方法是线程是否相互同步,以便析构函数看到两个线程的结果。无论如何都无法同步会产生竞争案例,因此用户有义务做到正确。
答案 1 :(得分:0)
在思考#1后,我确信§29.8.3
[atomics.fences]
中的这个论点{{1}}并不等同于:{/ p>
释放栏A与对原子执行获取操作的原子操作B同步 对象M如果存在原子操作X使得A在X之前被排序,则X修改M和B 读取由X写入的值或由假设释放序列X中的任何副作用写入的值 如果它是一个释放操作,它将会结束。
本段说明发布 fence 只能与获取操作同步。但是发布操作可以另外与使用操作同步。
答案 2 :(得分:0)
带有获取栅栏的void read_y_then_x()将栅栏放在错误的位置。它应该放在两个原子载荷之间。获取围栏基本上使得围栏上方的所有负载都有点像获取负载,除非在执行围栏之前不会建立之前发生的事件。