我有一个带有原子成员的类,我想编写一个复制构造函数:
struct Foo
{
std::atomic<int> mInt;
Foo() {}
Foo(const Foo& pOther)
{
std::atomic_store(mInt, std::atomic_load(pOther.mInt, memory_order_relaxed), memory_order_relaxed);
}
};但是我不知道我必须使用哪种排序,因为我不知道何时何地会调用这个复制构造函数。
我可以对复制构造函数和赋值操作符使用relaxed排序吗?
发布于 2013-11-13 18:23:12
不,如果您不知道它将如何使用,您应该使用memory_order_seq_cst来确保安全。如果使用memory_order_relaxed,您可能会遇到重新排序指令的问题。
发布于 2015-10-04 21:16:59
如果您的复制操作应该与其他线程上的其他操作同步,则只需要比memory_order_relaxed更强的内存排序。
然而,这种情况几乎从未发生过,因为线程安全的复制构造函数几乎总是需要一些外部同步或额外的互斥对象。
发布于 2017-09-05 01:11:30
std::atomic<T>模板删除它的复制构造函数,因为atomics用于共享状态,所以将它们复制到另一个原子通常不是您想要的。
删除复制构造函数会迫使类的用户考虑他们正在做什么,并记录他们正在执行一个值的原子加载,然后将该副本传递到其他地方。(如atomic<some_struct> var1 (var2.load()))。请参阅会员?
std::atomic<T> 本身并不是原子的的构造函数,所以担心在构造函数中为它排序是没有意义的(除非构造函数调用了一组其他函数,并将mInt的地址放在另一个线程可以得到的某个地方.)
更好的是,使用复制的值作为初始化器,而不是执行原子存储。(另见复制构造器中原子的非锁定复制方法)。
我认为这可能是一个问题的唯一方法是,如果您正在做一些已经没有定义的行为,比如使用new在一个已经共享的位置构造一个新的Foo对象,在这样做的时候可以由其他线程读取/编写。这显然是疯了,所以别那么做。
让类的内存排序行为与std::atomic<T>的构造函数匹配(即没有用于存储初始化程序)似乎是个好主意。
只有调用方知道来自源操作数的加载是否需要顺序一致性。因此,您应该让调用方通过接受内存顺序参数(为了与default=seq_cst的一致性,而不是因为这是任何人在这种情况下可能想要的)进行选择。是的,这是合法的C++:复制带有默认参数的构造函数
#include <atomic>
struct Foo
{
std::atomic<int> mInt;
Foo() {}
Foo(const Foo& pOther, std::memory_order order = std::memory_order_seq_cst)
: mInt(pOther.mInt.load(order))
{}
};这是我所期望的编译方式:为加载进行排序,但不对存储进行排序。(例如,查看ARM64的asm输出显示,load使用ldar进行获取加载,但是存储只是一个简单的str)。
我用调用方(戈德波特编译器浏览器)测试了它,它在堆栈上构造了一个地址,然后将其地址传递给一个非内联函数,该函数可能使其他线程可以使用该地址。所以它不能优化。
void extf(Foo &); // non-inline function
void test(const Foo *p) {
Foo tmp(*p);
extf(tmp);
}extf() 为使其他线程可以使用该地址所做的任何操作,都应该使用一个发布存储,以确保看到该地址的任何其他线程都会看到一个构造正确的 Foo**.** --这是一个正常的要求,也是为什么初始化器不是原子的完全正确的原因。
请注意,不可能以单个原子操作(在C++11或我所知道的任何硬件上)在两个不同的内存位置之间移动,所以强排序不太可能有用。
即使定义这样的移动是否是原子的,也是有问题的,因为原子性只存在于观察者的眼里。因为不可能同时观察两个内存位置,这是一个毫无意义的概念。(除非它们是相邻的,并且只需一个原子负载就可以得到它们)。
https://stackoverflow.com/questions/19961043
复制相似问题