关于多线程:c ++中的原子指针和在线程之间传递对象

Atomic pointers in c++ and passing objects between threads

我的问题涉及到std::atomic和这个指针指向的数据。如果在线程1中,我有

1
2
3
4
5
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)的分配会在其他线程看到两种情况下相同原子存储的分配之前被看到?

如果它有帮助或者很重要,我使用的是X64(我只关心这个平台),GCC(一个支持原子的版本)。


答案是肯定的,也许是否定的。

记忆模型原理:

C++ 11原子在默认情况下使用EDCOX1×0内存排序,这意味着操作是顺序一致的。

它的语义是,所有操作的顺序就好像所有这些操作都是按顺序执行的:

  • C++标准部分27.3/3解释了如何在原子学中工作:"在所有的MyMyYyOrthySeqQCST操作上应该有一个总的顺序S,与所有发生的位置的"发生之前"顺序和修改命令一致,这样每个MyMySyOrthOrthoSeqQCST加载值的操作根据此顺序s观察最后一次前面的修改,或者观察不是内存顺序的操作的结果。"

  • 第1.10/5节解释了这对非原子操作的影响:"库定义了一些特别标识为同步操作的原子操作(…)。这些操作在使一个线程中的分配对另一个线程可见方面发挥着特殊的作用。"

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

非原子数据风险

但是,您应该知道,实际上一致性保证对非原子值更为有限。

假设第一个执行场景:

1
2
3
4
(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之前进行的所有分配都被其他线程看到("发生在"约束之前)。

但是假设第二个执行场景:

1
2
3
4
(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。

您可以使用此方法进行实验验证,例如:

1
2
3
4
5
6
7
8
9
10
11
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是原子的。但这并不适用于所指的值。

需要注意的是,当您将原子指针重新分配给非原子指针时,特别是指针会带来进一步的同步风险。

例子:

1
2
3
4
5
6
7
8
9
// 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 !

默认情况下,C++ 11原子操作具有获取/释放语义。

因此,一个线程看到的是您的存储,也会看到它之前执行的所有操作。

你可以在这里找到更多的细节。