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

时间:2011-08-22 14:29:42

标签: ios multithreading macos gcc atomic

GCC为原子操作提供了一组很好的built-in functions。在MacOS或iOS上,甚至Apple提供nice set of atomic functions。但是,所有这些功能都执行一个操作,例如,加法/减法,逻辑运算(AND / OR / XOR)或比较和设置/比较和交换。我正在寻找的是一种原子地分配/读取int值的方法,如:

int a;
/* ... */    
a = someVariable;

这就是全部。 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 */

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

当然我可以使用__sync_fetch_and_add来读取变量(只需向其添加“0”,不修改其值),然后使用__sync_val_compare_and_swap来编写它(就像我一样)知道它的旧值,所以传递它将确保值始终交换),但这不会增加不必要的开销吗?

2 个答案:

答案 0 :(得分:3)

如果你希望你的负载是原子并且充当内存屏障,那么带有0参数的__sync_fetch_and_add确实是最好的选择。类似地,您可以使用带有0的and或带有-1的or来存储0和-1以原子方式存储内存障碍。对于写入,如果“获取”屏障足够,则可以使用__sync_test_and_set(实际上是xchg操作),或者如果使用Clang,则可以使用__sync_swap(这是一个具有完整屏障的xchg操作)。

然而,在许多情况下,这是过度的,您可能更喜欢手动添加内存屏障。如果你不想要内存屏障,你可以使用volatile load来原子地读/写一个对齐且不宽于单词的变量:

#define __sync_access(x) (*(volatile __typeof__(x) *) &(x))

(此宏是左值,因此您也可以将其用于__sync_store(x) = 0等商店。该函数实现了与C ++ 11 memory_order_consume形式相同的语义,但只有两个假设:

  • 你的机器有连贯的缓存;如果没有,您需要在加载之前(或在第一组加载之前)使用内存屏障或全局缓存刷新

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

在任何一种情况下,__sync_fetch_and_add都可以。据我所知,没有其他机器模仿Alpha,因此这两种假设都不会对当前的计算机造成问题。

答案 1 :(得分:2)

在大多数平台上,易失,对齐,字大小的读/写都是原子的。检查装配是查看平台上是否属实的最佳方法。原子寄存器不能产生与比较和交换等更复杂的机制一样多的有趣的等待自由结构,这就是它们被包含在内的原因。

请参阅http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.56.5659&rank=3了解理论。

关于带有0参数的synch_fetch_and_add - 这似乎是最安全的赌注。如果您担心效率,请分析代码并查看您是否达到了性能目标。你可能会成为过早优化的牺牲品。