C11 原子获取/释放和 x86_64 缺乏加载/存储一致性?

2024-02-17

我正在努力理解 C11 标准的第 5.1.2.4 节,特别是释放/获取的语义。我注意到https://preshing.com/20120913/acquire-and-release-semantics/ https://preshing.com/20120913/acquire-and-release-semantics/(除其他外)指出:

...释放语义可防止写入释放的内存重新排序以及按程序顺序位于其之前的任何读取或写入操作。

因此,对于以下情况:

typedef struct test_struct
{
  _Atomic(bool) ready ;
  int  v1 ;
  int  v2 ;
} test_struct_t ;

extern void
test_init(test_struct_t* ts, int v1, int v2)
{
  ts->v1 = v1 ;
  ts->v2 = v2 ;
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}

extern int
test_thread_1(test_struct_t* ts, int v2)
{
  int v1 ;
  while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v2 = v2 ;       // expect read to happen before store/release 
  v1     = ts->v1 ;   // expect write to happen before store/release 
  atomic_store_explicit(&ts->ready, true, memory_order_release) ;
  return v1 ;
}

extern int
test_thread_2(test_struct_t* ts, int v1)
{
  int v2 ;
  while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v1 = v1 ;
  v2     = ts->v2 ;   // expect write to happen after store/release in thread "1"
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
  return v2 ;
}

这些执行的地方:

>   in the "main" thread:  test_struct_t ts ;
>                          test_init(&ts, 1, 2) ;
>                          start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
>                          start thread "1" which does: r1 = test_thread_1(&ts, 4) ;

因此,我希望线程“1”具有 r1 == 1,线程“2”具有 r2 = 4。

我希望如此,因为(遵循第 5.1.2.4 节第 16 和 18 段):

  • 所有(非原子)读取和写入都“在之前排序”,因此“发生在”线程“1”中的原子写入/释放之前,
  • 其中“线程间发生在”线程“2”中的原子读取/获取之前(当它读取“true”时),
  • 反过来,它“在之前排序”,因此“发生在”(非原子)读取和写入(在线程“2”中)之前。

然而,我完全有可能没能理解这个标准。

我观察到为 x86_64 生成的代码包括:

test_thread_1:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  jne    <test_thread_1>  -- while is true
  mov    %esi,0x8(%rdi)   -- (W1) ts->v2 = v2
  mov    0x4(%rdi),%eax   -- (R1) v1     = ts->v1
  movb   $0x1,(%rdi)      -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
  retq   

test_thread_2:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  je     <test_thread_2>  -- while is false
  mov    %esi,0x4(%rdi)   -- (W2) ts->v1 = v1
  mov    0x8(%rdi),%eax   -- (R2) v2     = ts->v2   
  movb   $0x0,(%rdi)      -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
  retq   

And providedR1 和 X1 按此顺序发生,这给出了我期望的结果。

但我对x86_64的理解是,读取与其他读取按顺序发生,写入与其他写入按顺序发生,但读取和写入可能不按顺序发生。这意味着 X1 有可能在 R1 之前发生,甚至 X1、X2、W2、R1 按此顺序发生——我相信。 [这似乎极不可能,但如果 R​​1 因某些缓存问题而受阻?]

请问:我有什么不明白的地方吗?

我注意到,如果我改变负载/存储ts->ready to memory_order_seq_cst,为商店生成的代码是:

  xchg   %cl,(%rdi)

这与我对 x86_64 的理解一致,并且会给出我期望的结果。


x86's memory model is basically sequential-consistency plus a store buffer (with store forwarding). So every store is a release-store1. This is why only seq-cst stores need any special instructions. (C/C++11 atomics mappings to asm https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html). Also, https://stackoverflow.com/tags/x86/info https://stackoverflow.com/tags/x86/info has some links to x86 docs, including a formal description of the x86-TSO memory model https://www.cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.pdf (basically unreadable for most humans; requires wading through a lot of definitions).

既然您已经阅读了 Jeff Preshing 的优秀文章系列,我将向您指出另一篇更详细的文章:https://preshing.com/20120930/weak-vs-strong-memory-models/ https://preshing.com/20120930/weak-vs-strong-memory-models/

x86 上唯一允许的重新排序是 StoreLoad,而不是 LoadStore,如果我们用这些术语来谈论的话。 (如果负载仅部分与存储重叠,则存储转发可以做一些额外有趣的事情;全局不可见加载指令 https://stackoverflow.com/questions/50609934/globally-invisible-load-instructions,尽管你永远不会在编译器生成的代码中得到它stdatomic.)

@EOF 评论了英特尔手册中的正确引用:

英特尔® 64 和 IA-32 架构软件开发人员手册第 3 卷(3A、3B、3C 和 3D):系统编程指南,8.2.3.3 存储不会随着较早的加载而重新排序。


脚注1:忽略弱有序NT存储;这就是为什么你通常sfence做了NT店之后。 C11 / C++11 实现假设您没有使用 NT 存储。如果是的话,请使用_mm_sfence在发布操作之前确保它尊重您的 NT 存储。 (一般来说不要使用_mm_mfence / _mm_sfence在其他情况下 https://stackoverflow.com/questions/4537753/when-should-i-use-mm-sfence-mm-lfence-and-mm-mfence/50780314#50780314;通常你只需要阻止编译时重新排序。或者当然只使用stdatomic。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C11 原子获取/释放和 x86_64 缺乏加载/存储一致性? 的相关文章

  • 没有特殊字符的密码验证器

    我是 RegEx 的新手 已经进行了大量搜索 但没有找到任何具体内容 我正在编写一个验证密码字符串的正则表达式 可接受的字符串必须至少具有 4 种字符类型中的 3 种 数字 小写字母 大写字母 特殊字符 我对包含有一个想法 也就是说 如果这
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 通过引用传递 [C++]、[Qt]

    我写了这样的东西 class Storage public Storage QString key const int value const void add item QString int private QMap
  • C++11 删除重写方法

    Preface 这是一个关于最佳实践的问题 涉及 C 11 中引入的删除运算符的新含义 当应用于覆盖继承父类的虚拟方法的子类时 背景 根据标准 引用的第一个用例是明确禁止调用某些类型的函数 否则转换将是隐式的 例如最新版本第 8 4 3 节
  • -webkit-box-shadow 与 QtWebKit 模糊?

    当时有什么方法可以实现 webkit box shadow 的工作模糊吗 看完这篇评论错误报告 https bugs webkit org show bug cgi id 23291 我认识到这仍然是一个问题 尽管错误报告被标记为RESOL
  • 无限循环与无限递归。两者都是未定义的吗?

    无副作用的无限循环是未定义的行为 看here https coliru stacked crooked com view id 24e0a58778f67cd4举个例子参考参数 https en cppreference com w cpp
  • 用于 FTP 的文件系统观察器

    我怎样才能实现FileSystemWatcherFTP 位置 在 C 中 这个想法是 每当 FTP 位置添加任何内容时 我都希望将其复制到我的本地计算机 任何想法都会有所帮助 这是我之前问题的后续使用 NET 进行选择性 FTP 下载 ht
  • WPF 数据绑定到复合类模式?

    我是第一次尝试 WPF 并且正在努力解决如何将控件绑定到使用其他对象的组合构建的类 例如 如果我有一个由两个单独的类组成的类 Comp 为了清楚起见 请注意省略的各种元素 class One int first int second cla
  • 使用 C# 中的 CsvHelper 将不同文化的 csv 解析为十进制

    C 中 CsvHelper 解析小数的问题 我创建了一个从 byte 而不是文件获取 csv 文件的类 并且它工作正常 public static List
  • 结构体的内存大小不同?

    为什么第一种情况不是12 测试环境 最新版本的 gcc 和 clang 64 位 Linux struct desc int parts int nr sizeof desc Output 16 struct desc int parts
  • 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

    我在 C VS2012 NET 4 5 中的文本加密和解密方面遇到问题 具体来说 当我加密并随后解密字符串时 输出与输入不同 然而 奇怪的是 如果我复制加密的输出并将其硬编码为字符串文字 解密就会起作用 以下代码示例说明了该问题 我究竟做错
  • x:将 ViewModel 方法绑定到 DataTemplate 内的事件

    我基本上问同样的问题这个人 https stackoverflow com questions 10752448 binding to viewmodels property from a template 但在较新的背景下x Bind V
  • C 编程:带有数组的函数

    我正在尝试编写一个函数 该函数查找行为 4 列为 4 的二维数组中的最大值 其中二维数组填充有用户输入 我知道我的主要错误是函数中的数组 但我不确定它是什么 如果有人能够找到我出错的地方而不是编写新代码 我将不胜感激 除非我刚去南方 我的尝
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • C# 中的 IPC 机制 - 用法和最佳实践

    不久前我在 Win32 代码中使用了 IPC 临界区 事件和信号量 NET环境下场景如何 是否有任何教程解释所有可用选项以及何时使用以及为什么 微软最近在IPC方面的东西是Windows 通信基础 http en wikipedia org
  • C++ 继承的内存布局

    如果我有两个类 一个类继承另一个类 并且子类仅包含函数 那么这两个类的内存布局是否相同 e g class Base int a b c class Derived public Base only functions 我读过编译器无法对数
  • C++ 中的 include 和 using 命名空间

    用于使用cout 我需要指定两者 include
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么
  • 如何确定 CultureInfo 实例是否支持拉丁字符

    是否可以确定是否CultureInfo http msdn microsoft com en us library system globalization cultureinfo aspx我正在使用的实例是否基于拉丁字符集 我相信你可以使

随机推荐