原子读/写int值没有对int值本身的附加操作

Mec*_*cki 6 macos multithreading gcc atomic ios

GCC 为原子操作提供了一组很好的内置函数.在MacOS或iOS上,甚至Apple提供了一套很好的原子功能.然而,所有这些功能执行操作,例如加/减,逻辑运算(AND/OR/XOR)或比较和设置/比较和交换.我正在寻找的是一种原子分配/读取int值的方法,例如:

int a;
/* ... */    
a = someVariable;
Run Code Online (Sandbox Code Playgroud)

就这样.a将由另一个线程读取,并且重要的是a要么具有旧值或其新值.不幸的是,C标准并不保证分配或读取值是原子操作.我记得我曾经在某个地方读过,写入或读取类型变量的值int在GCC中保证是原子的(无论int的大小)但我在GCC主页上的任何地方都搜索过,我再也找不到这个语句了(也许它被删除了).

我无法使用,sig_atomic_t因为sig_atomic_t没有保证大小,也可能有不同的大小int.

由于只有一个线程会"写入"一个值a,而两个线程都将"读取"当前值a,我不需要以原子方式执行操作,例如:

/* thread 1 */
someVariable = atomicRead(a);
/* Do something with someVariable, non-atomic, when done */
atomicWrite(a, someVariable);

/* thread 2 */
someVariable = atomicRead(a);
/* Do something with someVariable, but never write to a */
Run Code Online (Sandbox Code Playgroud)

如果两个线程都要写入a,那么所有操作都必须是原子的,但这样,这可能只会浪费CPU时间; 我们项目中的CPU资源非常低.到目前为止,我们使用互斥锁进行读/写操作,a即使互斥锁保持这么短的时间,这已经导致了问题(其中一个线程是实时线程,并且互斥锁上的阻塞导致它失败实时约束,这是非常糟糕的).

当然我可以使用a __sync_fetch_and_add来读取变量(并简单地添加"0",不修改它的值)和写入使用a __sync_val_compare_and_swap来写它(因为我知道它的旧值,所以传递它将确保值总是交换),但这不会增加不必要的开销吗?

Pao*_*ini 3

__sync_fetch_and_add如果您希望负载是原子的充当内存屏障,那么带有0 参数的参数确实是最好的选择。同样,您可以使用andwith 0 或orwith -1 来通过内存屏障自动存储 0 和 -1。__sync_test_and_set对于编写,如果“获取”屏障足够,则可以使用(实际上是 xchg 操作),或者如果使用 Clang,则可以使用__sync_swap(这是具有完整屏障的 xchg 操作)。

然而,在许多情况下,这太过分了,您可能更愿意手动添加内存屏障。如果您不想要内存屏障,可以使用易失性加载来自动读取/写入对齐且宽度不超过一个字的变量:

#define __sync_access(x) (*(volatile __typeof__(x) *) &(x))
Run Code Online (Sandbox Code Playgroud)

(这个宏是一个左值,所以你也可以将它用于像 一样的存储__sync_store(x) = 0)。该函数实现与 C++11 形式相同的语义memory_order_consume,但仅基于两个假设:

  • 您的机器具有一致的缓存;如果没有,则需要在加载之前(或在一组加载的第一个加载之前)进行内存屏障或全局缓存刷新。

  • 您的机器不是 DEC Alpha。Alpha 对于重新排序内存访问具有非常宽松的语义,因此在加载之后(以及一组加载中的每个加载之后)您需要一个内存屏障。在 Alpha 上,上述宏仅提供memory_order_relaxed语义。顺便说一句,Alpha 的第一个版本甚至无法自动存储一个字节(只能存储一个字,即 8 个字节)。

无论哪种情况,都__sync_fetch_and_add可以。据我所知,没有其他机器模仿 Alpha,所以这两种假设都不会对当前的计算机造成问题。