C++中的原子指针和在线程之间传递对象

8yparm6h  于 2023-04-08  发布在  其他
关注(0)|答案(2)|浏览(109)

我的问题涉及std::atomic<T*>和这个指针指向的数据。

Object A;
std:atomic<Object*> ptr;
int bar = 2;
A.foo = 4;  //foo is an int;
ptr.store(*A);

如果在线程2中,我观察到ptr指向A,我能保证ptr->foo是4,bar是2吗?
原子指针的默认内存模型(顺序一致)是否保证在原子存储之前发生的非原子(在本例中为A.foo)上的赋值将在其他线程看到相同atomic.store的赋值之前被其他线程看到?
如果有帮助或重要,我使用x64(我只关心这个平台),gcc(支持原子的版本)。

kdfy810k

kdfy810k1#

答案是肯定的,也可能是否定的

内存模型原则:

C++11原子使用by defaultstd::memory_order_seq_cst内存排序,这意味着操作是顺序一致的
其语义是所有操作的排序就好像所有这些操作都是顺序执行的:

  • C++标准第29.3/3节解释了这对atomics是如何工作的:“* 所有memory_order_seq_cst操作应具有单个总顺序S,与所有受影响位置的顺序和修改顺序一致”发生在“”之前,使得加载值的每个memory_order_seq_cst操作根据该顺序S观察最后一次先前修改,或者观察不是memory_order_seq_cst的操作的结果。*”
  • 第1.10/5节解释了这如何影响非原子:“* 该库定义了许多原子操作(...),这些操作被专门标识为同步操作。这些操作在使一个线程中的分配对另一个线程可见方面起着特殊的作用。*”

你的问题的答案是肯定的!

非原子数据的风险

然而,您应该意识到,实际上,对于非原子值,一致性保证更加有限。
假设第一执行场景:

(thread 1) A.foo = 10; 
(thread 1) A.foo = 4;     //stores an int
(thread 1) ptr.store(&A); //ptr is set AND synchronisation 
(thread 2) int i = *ptr;  //ptr value is safely accessed (still &A) AND synchronisation

在这里,i是4。因为ptr是原子的,所以线程(2)在读取指针时安全地获得值&A。内存顺序确保所有在ptr之前进行的分配都被其他线程看到(“发生在”约束)。
但是假设第二执行场景:

(thread 1) A.foo = 4;     //stores an int
(thread 1) ptr.store(&A); //ptr is set AND synchronisation 
(thread 1) A.foo = 8;     // stores int but NO SYNCHRONISATION !! 
(thread 2) int i = *ptr;  //ptr value is safely accessed (still &A) AND synchronisation

这里的结果是未定义的。它可能是4,因为内存排序保证了在ptr赋值之前发生的事情被其他线程看到。但是没有什么可以阻止之后的赋值也被看到。所以它可能是8。
如果你有*ptr = 8;而不是A.foo=8;,那么你会再次确定:i是8。
你可以通过实验来验证这一点,例如:

void f1() {  // to be launched in a thread
    secret = 50; 
    ptr = &secret; 
    secret = 777; 
    this_thread::yield();
}
void f2() { // to be launched in a second thread
    this_thread::sleep_for(chrono::seconds(2));
    int i = *ptr; 
    cout << "Value is " << i << endl;
}

结论

总而言之,您的问题的答案是肯定的,但前提是同步后没有对非原子数据进行其他更改。主要风险是只有ptr是原子的。但这并不适用于所指向的值。
需要注意的是,当你将原子指针重新分配给非原子指针时,特别是指针会带来进一步的同步风险。
示例:

// Thread (1): 
std::atomic<Object*> ptr;
A.foo = 4;  //foo is an int;
ptr.store(*A);

// Thread (2): 
Object *x; 
x=ptr;      // ptr is atomic but x not !  
terrible_function(ptr);   // ptr is atomic, but the pointer argument for the function is not !
y4ekin9u

y4ekin9u2#

默认情况下,C++-11原子操作具有获取/释放语义。
所以一个线程看到你的store也会看到在它之前执行的所有操作。
你可以找到更多的细节here

相关问题