我开发了一个多线程程序,它依赖于 stdatomic.h 中 atomic_int、atomic_store 和 atomic_load 的可用性。该程序使用 GCC 编译。
现在,我尝试在几个缺少 stdatomic.h 的旧操作系统版本上编译该程序,但没有成功。不幸的是,要求我也能够在旧机器上编译程序。所以我在新操作系统版本上编译程序并在旧版本上运行二进制文件是不够的。
有没有办法在旧机器上模拟 stdatomic.h,或许使用一些特定于 GCC 的内置函数?
虽然在旧操作系统上安装较新版本的 GCC 可能是解决方案,但当前构建系统已将调用硬编码为“gcc”,而且新 GCC 必须从源代码编译为旧操作系统系统在包管理系统中没有它。因此,理想情况下,答案应该适用于旧的 GCC 版本。
最佳答案
虽然这不是适用于所有应用程序的完全嵌入式解决方案,但我找到了一种支持所需基本功能并至少通过一些基本多线程测试的方法:
#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 操作/秒(标准偏差 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
而 __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 变体似乎在我的机器和我的基准测试中运行得更快,即使它有更复杂的代码。奇怪的世界,不是吗?
关于c - 如何在缺少 stdatomic.h 的机器上拥有原子整数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42608242/