【问题标题】:Thread-safe atomic operations in gccgcc 中的线程安全原子操作
【发布时间】:2010-08-10 23:22:25
【问题描述】:

在我工作的一个程序中,我有很多代码如下:

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 开始)应该可以解决问题,这不是在这种情况下可以使用的唯一函数。
  • __atomic_store 或 __atomic_store_n 似乎更合适。

标签: multithreading gcc atomic


【解决方案1】:

您可以查看 gcc 文档。对于当前的 gcc 版本(4.3.2),它将是第 5.47 章 Built-in functions for atomic memory access - 对于其他 gcc 版本,请查看您的文档。它应该在第 5 章 - C 语言家族的扩展中。

顺便说一句,C 编译器绝对不保证简单的存储操作是原子的。你不能依赖这个假设。为了让机器操作码原子地执行,它需要 LOCK 前缀。

【讨论】:

  • 此外,ICC 还支持用于原子内存访问的内置函数(如果您打算编译器可移植)。对于 GCC,我认为从 4.1 开始就支持原子函数,所以一定要有一些 #ifdef 以确保 gcc 或 icc 版本!
  • 那么在并发访问的情况下,__sync_add_and_fetch 不可靠?我们必须直接使用 lock addx 和程序集内联?
【解决方案2】:

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

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

有一个very good explanation provided by Tudor Golubenco on his blog。他甚至为初始 atomic.h 文件提供了一个替代品,以防您有一些需要它的代码。

不幸的是,我是 stackoverflow 的新手,所以我只能在我的 cmets 中使用一个链接,所以请查看 Tudor 的帖子并获得启发。

【讨论】:

    【解决方案3】:

    在 x86 和大多数其他架构上,对齐的 4 字节读取和写入始终是原子的。不过,优化器可能会跳过/重新排序单个线程中的读取和写入。

    你想要做的是通知编译器其他线程可能已经触及了这个内存位置。 (pthread_mutex_lock 的副作用是告诉编译器其他线程可能已经触及内存的任何部分。)您可能会看到推荐的volatile,但这不在 C 规范中,并且 GCC 不会解释 volatile方式。

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

    是一种 GCC 特有的机制,表示“variable 已被写入,请重新加载”。

    【讨论】:

    • 除此之外,处理器的缓存可能会隐藏或重新排序相对于其他处理器的读取和写入......因此需要内存围栏。 pthread_mutex_lock 提供了这一点,但它非常依赖于架构。
    【解决方案4】:

    AFAIK,您不能在 MOV 指令前加上 LOCK;这仅适用于 RMW 操作。但是如果他确实使用了简单的存储,他可能还需要一个内存屏障,这对于互斥锁以及允许 LOCK 的指令都是隐含的。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • sig_atomic_t 仅在信号方面是原子的。它不是线程安全的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 1970-01-01
      相关资源
      最近更新 更多