gcc中的线程安全原子操作

17
在我工作的一个程序中,我有很多以下的代码:
pthread_mutex_lock( &frame->mutex );
frame->variable = variable;
pthread_mutex_unlock( &frame->mutex );

这明显是浪费 CPU 循环的行为,如果中间的指令可以用原子存储来替代的话。我知道 gcc 是很有能力做到这一点的,但是我找不到关于这种简单的线程安全原子操作的文档。我应该如何用原子操作来替换这段代码呢?
(我知道简单的存储操作理论上应该是原子的,但我不想只是希望优化器在处理过程中不会破坏它们的原子性。)
澄清一下:我不需要它们严格地是原子的;这些变量仅用于线程同步。也就是说,线程 B 读取该值,检查它是否正确,如果不正确,就会休眠。因此,即使线程 A 更新了该值,线程 B 没有意识到它已经更新,也没有问题,因为这只意味着线程 B 在不需要的时候休眠了,当它醒来时,该值将是正确的。

自内核2.6以来,当互斥量是自由的时候,它的成本几乎为零。无论如何,“__sync_lock_test_and_set”(自gcc 4.1以来)都应该能够解决问题,但这不是在这种情况下可以使用的唯一函数。 - claf
__atomic_store或__atomic_store_n似乎更合适。 - claf
这个回答解决了你的问题吗?标准库中有用于执行原子操作的函数吗? - undefined
5个回答

16

在某个程度上,C语言中的原子操作是通过atomic.h头文件直接从内核源代码提供的。

然而,在用户空间代码中直接使用内核头文件是一种非常不好的做法,因此atomic.h头文件在一段时间前已被删除。现在我们可以使用“GCC Atomic Builtins”,这是一种更好、更可靠的方法。

Tudor Golubenco在他的博客中提供了非常好的解释。他甚至提供了一个替代初始atomic.h文件的快速解决方案,以防您有些需要它的代码。

不幸的是,我是stackoverflow的新手,所以我的评论只能使用一个链接,请查看Tudor的文章,并获得启发。


16
你可以查看gcc文档。对于当前的gcc版本(4.3.2),请参阅第5.47章原子内存访问的内置函数 - 对于其他gcc版本,请查看你的文档。它应该在第5章-C语言家族扩展中。 顺便提一下,C编译器绝不保证简单存储操作是原子的。你不能依赖这个假设。为了使机器指令以原子方式执行,需要使用LOCK前缀。

2
此外,如果您打算编译器可移植,ICC也支持原子内存访问的内置函数。 对于GCC,我认为自4.1以来已经支持原子函数,因此请确保有一些#ifdef来确保gcc或icc版本! - claf
那么在并发访问的情况下,__sync_add_and_fetch 不可靠吗?我们必须直接使用内联汇编锁定 addx 吗? - Bionix1441

4

在x86和大多数其他架构中,4字节对齐的读写操作总是原子性的。但是优化器可能会在单个线程内跳过/重新排序读写操作。

你需要做的是告诉编译器其他线程可能已经访问了这个内存位置。(pthread_mutex_lock的副作用是告诉编译器其他线程可能已经访问了内存的任何部分。)你可能会看到volatile被推荐使用,但这不在C规范中,而且GCC也不会以那种方式解释volatile

asm("" : "=m" (variable));
frame->variable = variable;

__builtin_reload是GCC特有的机制,用于表示“variable已经被写入,重新加载它”。


9
除了这些,处理器的缓存可能会隐藏或重新排序相对于其他处理器的读写操作...因此需要内存栅栏。pthread_mutex_lock提供了这个功能,但它非常依赖于体系结构。 - ephemient

1
据我所知,你不能在MOV指令前加上LOCK前缀;这只允许用于RMW操作。但是如果他使用简单的存储,他可能还需要一个内存屏障,这是互斥锁隐含的,也适用于允许LOCK的指令。

0

从我所看到的,您正在使用GNU平台进行开发,因此可以肯定地说glic提供了一个具有原子能力的int数据类型,'sig_atomic_t'。因此,这种方法可以确保您在内核级别上进行原子操作,而不是在gcc级别上。


sig_atomic_t 只与信号有关的原子操作。它不是线程安全的。 - Henri Menke

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