C语言中使用MSVC进行原子加载

10
TL;DR: 我需要 Microsoft C (不是 C++) 的等同于 C11 的 atomic_load。有人知道正确的函数是什么吗?
我有一些使用原子操作的标准代码,类似于:
do {
  bar = atomic_load(&foo);
  baz = some_stuff(bar);
} while (!atomic_compare_exchange_weak(&foo, &bar, baz));

我正在尝试使用MSVC处理它。CAS(InterlockedCompareExchange)很容易,但atomic_load似乎更麻烦。

也许我漏掉了什么,但是MSDN上的同步函数列表似乎没有任何关于简单加载的内容。我唯一能想到的是像InterlockedOr(object, 0)这样的东西,这将为每次加载生成一个存储(更不用说栅栏了)...

只要变量是易失性的,我认为只需读取该值即可安全,但如果我这样做,Visual Studio的代码分析功能会发出一堆C28112警告(“通过Interlocked函数访问的变量(foo)必须始终通过Interlocked函数访问。”)。

如果简单读取确实是正确的方法,我认为我可以使用类似以下内容来消除这些警告:

#define atomic_load(object) \
  __pragma(warning(push)) \
  __pragma(warning(disable:28112)) \
  (*(object)) \
  __pragma(warning(pop))

但分析器坚持认为我应该始终使用Interlocked*函数,这让我相信一定有更好的方法。如果是这样,那么是什么呢?


没有选择,必须支持MSVC :(. 如果我只需要做 #define atomic_load(object) (*(object)) (这是我过去为MSVC所做的),但代码分析器让我有些犹豫。 - nemequ
1个回答

7
我认为在这里忽略分析器是可以接受的,因为文档表明,在寄存器宽度变量的简单读取上是安全的(32位系统上为32位,64位系统上为64位)。警告文档本身基本上说它过于谨慎,即使访问可能是安全的。

话虽如此,如果你想消除警告,你总是可以使用幂等的Interlocked操作来获得所需的行为。例如,你可以定义:

#define atomic_load(object) InterlockedOr((object), 0)

由于与0进行按位或运算永远不会改变值,它总是返回原始值,因此最终结果是在同时未写入任何内容的情况下读取原始值。
如果您正在使用memory_order_relaxed模拟atomic_load_explicit,则可以通过使用InterlockedOrNoFence避免内存屏障以获得更好的性能,但是对于模拟默认(顺序一致)的atomic_load,您应该坚持使用InterlockedOrInterlockedOr的选择基本上是随意的(理论上,它可能比带进位的加法或减法更快),但是使用0的InterlockedXor应该表现相同,只要它们使用其身份值进行操作,其他几个操作也是如此。
您还可以类似地使用InterlockedCompareExchange;需要测试以确定哪个更快:
#define atomic_load(object) InterlockedCompareExchange((object), 0, 0)

在这里,如果值已经是0,你将它设置为0,但你真正使用它的目的只是为了获取返回值,也就是无操作交换之前的原始值。


正如我在问题中提到的那样,我对InterlockedOr持怀疑态度,因为它可能会导致虚假写入,尽管编译器可能足够聪明以优化它。InterlockedCompareExchange是一个很酷的想法,我之前没有考虑过。既然它真的是安全的,我认为最好的解决方案是我的问题中的最后一段代码(使用禁用警告的pragma)。由于我正在循环中执行CAS操作,因此从加载中出现虚假失败(例如在32位机器上读取64位值时出现的损坏读取)并不是世界末日。 - nemequ
@nemequ:我发誓我读了两遍问题,完全错过了提到 InterlockedOr 的那一段。 对此感到抱歉。很高兴我加了另一个选项。 :-) - ShadowRanger
1
_InterlockedCompareExchange(至少在x86 / x64上)几乎肯定会生成一个lock cmpxchg指令,无论比较是否成功,都会写入对象([cmpxchg](https://www.felixcloutier.com/x86/cmpxchg#description))。这使得使用`_InterlockedCompareExchange`来处理应该能够接受只读对象的函数是不正确的。 - doodspav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接