系统调用(含futex)是否隐含内存屏障/顺序一致读?及代码优化疑问
C++多线程Futex内存序问题分析
在C++多线程场景中,两个线程的操作顺序相反:线程1先存储please_wake_me_up,再尝试省略原子cnt的load后调用futex_wait;线程2先存储cnt,再加载please_wake_me_up决定是否调用futex_wake。
代码实现如下:
#include <linux/futex.h> #include <atomic> static long futex(uint32_t* uaddr, int futex_op, uint32_t val, const struct timespec* timeout, uint32_t* uaddr2, uint32_t val3) { return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); } static long futex_wake(uint32_t* uaddr) { return futex(uaddr, FUTEX_WAKE_PRIVATE, 1, nullptr, nullptr, 0); } static long futex_wait(uint32_t* uaddr, uint32_t val) { return futex(uaddr, FUTEX_WAIT_PRIVATE, val, nullptr, nullptr, 0); } std::atomic<bool> please_wake_me_up{false}; uint32_t cnt{0}; void thread_1() { std::atomic_ref atomic_cnt(cnt); please_wake_me_up.store(true, std::memory_order_seq_cst); atomic_cnt.load(std::memory_order_seq_cst); // <-- 此行是否可省略? futex_wait(&cnt, 0); // <-- 系统调用内部会读取计数器,其内存序是什么? } void thread_2() { std::atomic_ref atomic_cnt(cnt); atomic_cnt.store(1, std::memory_order_seq_cst); if (please_wake_me_up.load(std::memory_order_seq_cst)) { futex_wake(&cnt); } }
当四个原子操作均采用std::memory_order_seq_cst时,可保证至少一个线程能看到对方的存储操作,这是预期目标。基于futex系统调用内部会读取目标变量的特性,提出以下疑问:
疑问列表
- 所有系统调用是否都会触发编译器内存屏障?
- 系统调用通常是否等同于全内存屏障?
- 省略thread_1中标记的
atomic_cnt.load行是否安全?futex内部的读取是否保证顺序一致性?
- 省略thread_1中标记的
- 若必须保留该行,使用
std::atomic_thread_fence(std::memory_order_seq_cst)是否更合适?
- 若必须保留该行,使用
以下针对x86_64和arm64架构进行解答:
问题1:所有系统调用是否都会触发编译器内存屏障?
是的。编译器无法穿透系统调用的黑盒,会确保系统调用执行前,所有代码中声明的内存加载/存储操作都已提交,不会将系统调用前后的内存操作重排。系统调用对编译器而言相当于编译器屏障,阻止跨调用的指令重排。
问题2:系统调用通常是否等同于全内存屏障?
分架构讨论:
- x86_64:绝大多数系统调用会隐式触发全内存屏障(等效于
mfence指令效果),内核进入时会执行序列化操作,确保系统调用前的所有内存操作完成,且调用后的内存操作不会提前执行。futex属于需要严格内存序的系统调用,必然触发全屏障。 - arm64:Linux内核规定所有系统调用都会执行
dmb sy指令(全内存屏障),确保系统调用前后的内存操作按顺序可见,因此也等同于全内存屏障。
问题3:省略thread_1中标记的atomic_cnt.load行是否安全?futex内部的读取是否保证顺序一致性?
不安全,不能省略。原因如下:
- 虽然
futex_wait系统调用本身会触发内存屏障,但please_wake_me_up.store(true)与futex_wait内部对cnt的读取之间,缺少顺序一致性(seq_cst)的同步点。原代码中的atomic_cnt.load(seq_cst)用于构建seq_cst全局同步顺序,确保please_wake_me_up的存储对线程2可见,或线程2对cnt的存储对线程1可见。 futex_wait内部对cnt的读取是内核级普通读取,不具备C++原子操作的seq_cst语义,无法参与用户态的seq_cst全局同步链。省略该load可能导致两个线程的存储操作互相不可见,线程1永久阻塞。
问题4:若必须保留该行,使用std::atomic_thread_fence(std::memory_order_seq_cst)是否更合适?
是的,这是更优选择:
- 两者都能提供seq_cst级别的同步效果,确保
please_wake_me_up.store不会被重排到屏障之后,同时参与全局seq_cst顺序构建。 - 原子load操作会产生一次内存访问,而单纯的内存栅栏不会产生额外的内存读写,性能更高效,尤其适合高并发场景。
内容的提问来源于stack exchange,提问作者sedor
相关产品推荐
相关产品推荐

