You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程无锁场景下两个共享变量原子更新实现方案咨询

核心结论

单独使用内存屏障无法实现需求。内存屏障仅能约束CPU、编译器的指令重排顺序,无法将两次独立的内存写操作转换为对读者原子可见的单个操作,读者依然可能读到两次写中间的混搭状态。
针对你单写多读、读操作极高频次的场景,不需要加互斥锁,以下两种无锁方案都可以实现a、b的一致同步,读路径开销极低。

可行无锁实现方案

方案1:16字节对齐原子双字操作(通用64位环境首选)

你定义的结构体包含一个8字节指针void* a和一个8字节无符号整数uint64_t b,总大小恰好16字节。当前所有主流64位架构(x86_64、ARMv8及以上)都原生支持16字节的原子读写操作,配合C11/C++11标准的原子接口,可直接将整个结构体作为单个原子变量操作,实现逻辑最简单。
示例代码(C语言):

#include <stdatomic.h>
#include <stdint.h>

// 强制16字节对齐,满足16字节原子操作的硬件要求
typedef struct __attribute__((aligned(16))) {
    void* a;
    uint64_t b;
} SyncData;

_Atomic SyncData shared_data;

// 写线程调用:原子更新两个变量
void write_sync_data(void* new_a, uint64_t new_b) {
    SyncData new_val = {.a = new_a, .b = new_b};
    // release内存序:保证结构体内容写入先于原子存储对读者可见
    atomic_store_explicit(&shared_data, new_val, memory_order_release);
}

// 读线程调用:原子读取一致的两个变量
SyncData read_sync_data(void) {
    // acquire内存序:和写端release配对,保证读到完整写入的结构体
    return atomic_load_explicit(&shared_data, memory_order_acquire);
}

该方案读路径是单次原子加载,x86_64架构下开销和读取普通指针几乎没有差异,完全适配高频读场景。目前GCC 4.8+、Clang 3.3+、MSVC 2012+在64位环境下都默认支持该特性,不需要额外依赖。

方案2:SeqLock序列锁(极端读性能/兼容特殊环境首选)

如果运行环境不支持16字节原子操作(比如老32位架构、特殊嵌入式场景),或者你追求极致的读性能,单写多读场景下SeqLock是最优选择,读路径完全没有共享变量写操作,不会触发多核缓存行失效,读性能比原子双字操作更高。
核心逻辑是通过一个原子版本号标记写操作的起止,读者通过校验前后读取的版本号一致性,过滤掉写操作中间的混搭状态。
示例代码(C语言):

#include <stdatomic.h>
#include <stdint.h>

void* shared_a;
uint64_t shared_b;
_Atomic uint64_t seq_lock = 0;

// 写线程调用:更新两个变量
void write_sync_data(void* new_a, uint64_t new_b) {
    uint64_t old_seq = atomic_load_explicit(&seq_lock, memory_order_relaxed);
    // 版本号置为奇数,标记正在写入
    atomic_store_explicit(&seq_lock, old_seq + 1, memory_order_release);
    atomic_thread_fence(memory_order_release);
    // 写入实际数据
    shared_a = new_a;
    shared_b = new_b;
    atomic_thread_fence(memory_order_release);
    // 版本号置为偶数,标记写入完成
    atomic_store_explicit(&seq_lock, old_seq + 2, memory_order_release);
}

// 读线程调用:读取一致的两个变量
void read_sync_data(void** out_a, uint64_t* out_b) {
    uint64_t seq1, seq2;
    do {
        seq1 = atomic_load_explicit(&seq_lock, memory_order_acquire);
        // 读到写入中状态,直接重试
        if (seq1 & 1) continue;
        atomic_thread_fence(memory_order_acquire);
        *out_a = shared_a;
        *out_b = shared_b;
        atomic_thread_fence(memory_order_acquire);
        seq2 = atomic_load_explicit(&seq_lock, memory_order_acquire);
        // 前后版本号一致,说明读过程没有写入,数据有效
    } while (seq1 != seq2);
}

因为你只有1个写线程,写操作频率远低于读操作时,读者触发重试的概率极低,整体性能非常高。

常见误区说明

不要尝试仅通过“写端加写屏障、读端加读屏障”的方式实现同步:

  • 写端即使严格按顺序更新a、b再插入屏障,依然存在时间窗口:写端刚写完a还未写b时,读线程切入读取,就会拿到新a+旧b的混搭值
  • 内存屏障只能保证当前线程的内存操作可见性顺序,无法把多个独立写操作打包成读者视角的原子操作,必须配合原子状态标记(要么整体原子读写,要么用版本号标记写操作的起止)才能过滤掉中间状态。
选型建议
  • 常规64位服务端、桌面环境优先选16字节原子方案,实现简单不易出错
  • 读压力极大、CPU核数较多,或者运行环境不支持16字节原子时,选SeqLock方案

内容的提问来源于stack exchange,提问作者qiaochu li

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:06:20