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

时间:2017-03-05 12:15:10

标签: c multithreading gcc atomic

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

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

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

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

2 个答案:

答案 0 :(得分: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)

__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)

但是,性能始终从139280.5 ops /秒(标准差1799.6 ops /秒)降至131805.6 ops /秒(标准差986.03 ops /秒)。因此,降低的性能具有统计意义。

编辑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

__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

...并且在具有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

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

asm volatile ("mfence" ::: "memory");

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

编辑3:

显然,__sync_fetch_and_add没有被优化掉,因为轮询变量的循环具有此程序集输出:

.L29:
        xorl    %eax, %eax
        lock
        xaddl   %eax, (%rdi)
        testl   %eax, %eax
        je      .L29

改为:

#define atomic_load(object) ((object)->__val)

你会得到:

.L29:
        movl    (%rdi), %eax
        testl   %eax, %eax
        je      .L29

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

.L38:
        movl    (%rdi), %eax
        testl   %eax, %eax
        je      .L38

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

答案 1 :(得分:1)

最好的办法是推出自己的包装纸。在可用时使用stdatomic,否则使用互斥锁或平台特定指令模拟操作。