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

CPU原生不支持的大尺寸std::atomic是如何实现的?

C++大尺寸std::atomic的实现原理

问题描述

当前C++编译器支持CPU原生不支持的大尺寸原子操作,例如x64平台原生支持16字节原子操作,但std::atomic可处理更大的结构体。以下为示例代码:

#include <iostream>
#include <atomic>

using namespace std;

struct S { size_t a, b, c; };

atomic<S> apss;

int main()
{
    auto ref = apss.load( memory_order_relaxed );
    apss.compare_exchange_weak( ref, { 123, 456, 789 } );
    cout << sizeof ::apss << endl;
}

在我的平台上,cout始终输出32。但这类原子操作无需mutex是如何工作的?通过反汇编无法找到线索。

若使用MSVC++运行以下代码:

#include <atomic>
#include <thread>
#include <array>

using namespace std;

struct S { size_t a, b, c, d, e; };

atomic<S> apss;

int main()
{
    array<jthread, 2> threads;
    auto threadFn = []()
    {
        auto ref = apss.load( memory_order_relaxed );
        for( size_t i = 10'000'000; i--; apss.compare_exchange_weak( ref, { } ) );
    };
    threads[0] = jthread( threadFn );
    threads[1] = jthread( threadFn );
}

该代码几乎不消耗内核时间,竞争完全发生在用户态,推测这可能基于软件事务内存实现。

解答

核心实现机制

当原子变量的类型大小超出CPU原生支持的原子操作宽度(如x64平台的16字节上限)时,编译器会采用用户态无锁方案替代内核态mutex,主要有两种实现路径:

  1. 软件事务内存(STM)
    你观察到MSVC下低内核消耗的现象确实对应STM实现。STM在用户态跟踪线程对内存的访问操作,当检测到多个线程竞争同一原子变量时,会直接在用户态执行重试或回滚逻辑,完全不需要调用内核的mutex接口,因此几乎不会产生内核态耗时。

  2. 乐观锁+CAS循环的复合结构
    部分编译器会给大尺寸原子变量附加一个隐藏的"版本标记"或"锁状态"字段——这就是你看到sizeof(apss)输出32的原因:结构体S本身占24字节,加上额外8字节的标记字段后对齐到32字节。操作流程如下:

    • 读取时同时获取数据内容和对应的版本标记
    • 修改数据后,检查版本标记是否未被其他线程修改,若未变化则通过CAS原子更新数据和标记的组合
    • 若标记已变化,说明存在竞争,直接重试整个操作

反汇编无线索的原因

编译器会将大尺寸std::atomic的操作封装为标准库的内部函数调用(比如MSVC下的_Atomic_load、_Atomic_compare_exchange_weak系列函数)。仅反汇编主函数只能看到这些库函数的调用指令,无法直接看到内部的无锁或STM逻辑。需要反汇编对应的标准库二进制文件(如vcruntime140.dll)才能查看具体实现细节。


内容的提问来源于stack exchange,提问作者Edison von Myosotis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 13:51:24