如何在缺少stdatomic.h的机器上使用原子整数?

juh*_*ist 6 c multithreading gcc atomic

我开发了一个多线程程序,它取决于stdatomic.h中atomic_int,atomic_store和atomic_load的可用性.该计划由GCC编制.

现在,我试图在几个缺少stdatomic.h的旧操作系统版本上编译程序失败.不幸的是,我需要能够在旧机器上编译程序.因此,我在新的操作系统版本上编译程序并在旧版本上运行二进制文件是不够的.

有没有办法在旧机器上模拟stdatomic.h,也许有一些特定于GCC的内置函数?

虽然在旧的操作系统上安装较新版本的GCC可能是解决方案,但是当前的构建系统已经硬编码到其上的"gcc",并且新的GCC必须从源代码编译,因为旧操作系统不应该在包管理系统中有它.因此,理想情况下,答案将适用于旧的GCC版本.

juh*_*ist 3

虽然这并不是适用于所有应用程序的完全嵌入式解决方案,但我找到了一种支持所需基本功能并至少通过一些基本多线程测试的方法:

#define _Atomic(T) struct { volatile __typeof__(T) __val; }

typedef _Atomic(int) atomic_int;

#define atomic_load(object) \
    __sync_fetch_and_add(&(object)->__val, 0)

#define atomic_store(object, desired) do { \
    __sync_synchronize(); \
   (object)->__val = (desired); \
    __sync_synchronize(); \
} while (0)
Run Code Online (Sandbox Code Playgroud)

__sync_synchronize 和 __sync_fetch_and_add 调用是必要的,否则线程之间的通信将失败(我没有测试仅删除其中一个,我只是测试了删除两者)。

然而,我不太有信心这个解决方案适用于所有情况。我从https://gist.github.com/nhatminhle/5181506找到它,其中作者不推荐将它用于旧的 GCC 版本。

理论上,您还可以使用互斥体。然而,互斥锁的性能比原子差。

编辑:

也可以通过以下方式实现atomic_store:

#define atomic_store(object, desired) do { \
    for (;;) \
    { \
        __typeof__((object)->__val) oldval = atomic_load(object); \
        if (__sync_bool_compare_and_swap(&(object)->__val, oldval, desired)) \
        { \
            break; \
        } \
    } \
} while (0)
Run Code Online (Sandbox Code Playgroud)

但是,这会持续将性能从 139280.5 操作/秒(标准偏差 1799.6 操作/秒)降低到 131805.6 操作/秒(标准偏差 986.03 操作/秒)。因此,性能下降具有统计意义。

编辑2:

循环方法具有以下汇编代码:

.globl signal_completion
        .type   signal_completion, @function
signal_completion:
.LFB18:
        leaq    4(%rdi), %rcx
.L42:
        xorl    %eax, %eax
        lock
        xaddl   %eax, (%rcx)
        movl    $1, %edx
        movl    %eax, -4(%rsp)
        movl    -4(%rsp), %eax
        lock
        cmpxchgl        %edx, (%rcx)
        jne     .L42
        rep ; ret
.LFE18:
        .size   signal_completion, .-signal_completion
        .p2align 4,,15
Run Code Online (Sandbox Code Playgroud)

而 __sync_synchronize 方法具有以下代码:

.globl signal_completion
        .type   signal_completion, @function
signal_completion:
.LFB18:
        movl    $1, 4(%rdi)
        ret
.LFE18:
        .size   signal_completion, .-signal_completion
        .p2align 4,,15
Run Code Online (Sandbox Code Playgroud)

...并且在具有 stdatomic.h 的机器上,它编译为:

        .globl  signal_completion
        .type   signal_completion, @function
signal_completion:
.LFB43:
        .cfi_startproc
        movl    $1, 4(%rdi)
        mfence
        ret
        .cfi_endproc
.LFE43:
        .size   signal_completion, .-signal_completion
Run Code Online (Sandbox Code Playgroud)

所以,我唯一真正缺乏的是mfence。我想它可以使用简单的内联汇编来添加,例如:

asm volatile ("mfence" ::: "memory");
Run Code Online (Sandbox Code Playgroud)

...放置在atomic_store 定义中的第二个__sync_synchronize() 之后。

编辑3:

显然, __sync_fetch_and_add 没有被优化掉,因为轮询变量的循环具有以下汇编输出:

.L29:
        xorl    %eax, %eax
        lock
        xaddl   %eax, (%rdi)
        testl   %eax, %eax
        je      .L29
Run Code Online (Sandbox Code Playgroud)

通过改为:

#define atomic_load(object) ((object)->__val)
Run Code Online (Sandbox Code Playgroud)

你会得到:

.L29:
        movl    (%rdi), %eax
        testl   %eax, %eax
        je      .L29
Run Code Online (Sandbox Code Playgroud)

这相当于在支持 stdatomic.h 的机器上的程序集:

.L38:
        movl    (%rdi), %eax
        testl   %eax, %eax
        je      .L38
Run Code Online (Sandbox Code Playgroud)

奇怪的是, __sync_fetch_and_add 变体似乎在我的机器和我的基准测试上运行得更快,尽管它有更复杂的代码。奇怪的世界,不是吗?