对原子结构和指针的误解

Misunderstanding of atomic structs and pointers

本文关键字:误解 指针 原子结构      更新时间:2023-10-16

我的第一个问题是:有没有办法访问atomic<struct>对象中的结构成员?例如,我收到编译器错误:

struct std::atomic<node>’ has no member named ‘data’ a.data = 0; 

在此部分中

struct node{
  int data;
  node* next;
};
int main(){
  atomic<node> a;
  a.data = 0;
}

我可以通过创建一个临时节点来解决它,如下所示:

  atomic<node> a;
  node temp;
  temp.data = 0;
  a.store(temp);

但这似乎不是很优雅。

第二个问题是,如果我有一个指向原子对象的指针怎么办? 是否有直接访问节点成员的方法? 显然以下内容无法编译,我将如何将其更改为在 b 处的节点值中存储 0?

atomic<node> b = new node;
b->data = 0;

这是我找到的解决方案,但同样,有没有更优雅的方法

atomic<node> *b;
node temp;
temp.data = 0;
b->store(&temp);

最后,atomic<node*>atomic<node>*有什么区别

这个[解决方法]似乎不是很优雅。

std::atomic<T>不能使任意操作原子化:仅支持加载和存储数据。这就是为什么你的"解决方法"实际上是处理原子对象的方法:你以任何你喜欢的方式准备新的node值,然后原子地将其设置为atomic<node>变量。

如果我有一个指向原子对象的指针怎么办?是否有直接访问节点成员的方法?

通过指针访问节点的内容也不是原子的:由于std::atomic<T>可以保证只加载和存储其值是原子的,因此它不允许在不进行显式复制的情况下访问T的成员。这是一件好事,因为它可以防止代码的读者产生错误的印象,即对T内部的访问在某种程度上是原子的。

atomic<node*>atomic<node>*有什么区别

在 firs 的情况下,原子对象存储一个指针,可以通过原子方式访问该指针(即,您可以将此指针重新指向原子新节点)。在第二种情况下,原子对象存储可以通过原子方式访问的值,这意味着您可以以原子方式读取和写入整个node

当你这样做时

atomic<node> a;
node temp; // use a.load() to copy all the fields of a to temp
temp.data = 0;
a.store(temp);

您丢失了下一个字段的值。我会做出建议的更改。如果节点是一个简单的类型,如 std::atomic_int,我认为使用"="运算符是可能的。否则不会。我认为您的情况没有其他解决方法。

最后,原子<节点*>和原子有什么区别 <节点>*?

如果使用原子<节点*>则对节点对象的地址执行的操作将是原子的,而在另一种情况下,您需要为原子对象分配内存,并且在实际节点对象上执行的操作将是原子的。

请注意,您的"解决方案"包括除.data以外的所有成员的非原子读-修改-写。

atomic<node> a;
node temp = a.load();
temp.data = 0;
a.store(temp); // steps on any changes to other member that happened after our load

如果需要一个结构,您可以在其中以原子

方式一起更新所有成员,也可以单独以原子方式修改其中一个成员(不对整个结构进行compare_exchange_weak),则可以使用原子结构和具有两个原子成员的结构的联合。 例如,这对于双链表中的两个指针或指针 + 计数器可能很有用。 当前的编译器甚至不擅长读取原子结构的一个成员而不做一些缓慢的事情,例如使用 CMPXCHG16B 加载整个结构,然后只查看一个成员。 (即使使用 gcc6.2 也是如此 memory_order_relaxed )。

此联合黑客仅在您使用C++编译器时才有效,该编译器保证编写一个工会成员然后读取另一个成员是可以的,就像在 C99 中一样。

这适用于最大为硬件可以 cmpxchg 的最大大小的结构,即 x86-64 上的 16B(如果您在 gcc 中启用 -mcx16,以使用第一代 K8 CPU 不支持的CMPXCHG16B,因此它在技术上不是基准 x86-64)。

对于较大的结构,atomic<the_whole_thing>不会是无锁的,并且通过atomic<int>另一个工会成员读取/写入其成员将不安全。 不过,阅读可能仍然可以。

这可能会使内存排序语义变得混乱,因为即使是强排序的 x86 也可以对具有更宽负载的窄存储重新排序,该存储完全包含它。 如果您主要只需要原子性,那就太好了,但是在只编写一个成员的同一线程中读取完整对象(例如,在执行 cmpxchg 时)需要 x86 上的 MFENCE,即使对于获取/发布语义也是如此。 您将始终看到自己的存储,但如果其他线程存储到同一对象,它们可以观察到您的存储在您加载后发生。