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

std::atomic<T>::exchange示例中counter值为何小于25?

问题背景

参考std::atomic<T>::exchange官方示例的代码如下:

int main(){
    const std::size_t ThreadNumber = 5;
    const int Sum = 5;
    std::atomic<int> atom{0};
    std::atomic<int> counter{0};
 
    // lambda as thread proc
    auto lambda = [&](const int id){
        for (int next = 0; next < Sum;){
            // each thread is writing a value from its own knowledge
            const int current = atom.exchange(next);
            counter++;
            // sync writing to prevent from interrupting by other threads
            std::osyncstream(std::cout)
                << '#' << id << " (" << std::this_thread::get_id()
                << ") wrote " << next << " replacing the old value "
                << current << '\n';
            next = std::max(current, next) + 1;
        }
    };
 
    std::vector<std::thread> v;
    for (std::size_t i = 0; i < ThreadNumber; ++i){
        v.emplace_back(lambda, i);
    }
 
    for (auto& tr : v){
        tr.join();
    }
 
    std::cout << ThreadNumber << " threads adding 0 to "
              << Sum << " takes total "
              << counter << " times\n";
}

初始认知存在偏差:认为程序启动5个线程,每个线程循环执行5次,原子变量counter最终值应为25。但实际运行时counter值随线程调度波动,官方示例运行结果为16,本地运行也始终达不到25。

核心原因

你的前提假设每个线程固定执行5次循环完全不成立。counter本身是原子变量,计数逻辑没有任何并发错误,数值偏小是代码本身的设计导致的:

  • 单线程的循环退出条件是局部变量next >= Sum(即next≥5),但next从来不是单线程独立逐次自增的:所有线程通过atom.exchange()的原子读改写操作共享状态,每次执行完exchange后,线程会把自己的next更新为max(从atom读到的旧值, 自身当前next值) + 1。只要线程读到其他线程写入的更大值,自身的next会直接跳涨,不需要一步步从0数到4。
  • 这段代码的逻辑根本不是5个线程各自独立完成0到4的计数,而是5个线程共同争抢执行权,协作把共享状态关联的递增序列从0推到5。每进入一次循环就会执行一次exchange、对应一次counter++,counter统计的是所有线程完成这个共同任务的总原子操作尝试次数,不是每个线程独立循环次数的累加。

举个典型的调度场景:

  1. 线程1率先执行,第一次exchange读到初始值0,写入自己的next=0,随后把自己的next更新为1
  2. 线程1还没跑下一轮,线程2启动执行,第一次exchange读到0,写入自己的初始next=0,随后把自己的next更新为1
  3. 线程1抢到执行权跑第二轮,exchange读到线程2写入的0,写入自己的next=1,随后把自己的next更新为2
  4. 刚启动的线程3抢到执行权,exchange读到线程1刚写入的1,写入自己的初始next=0,随后直接把自己的next更新为max(1,0)+1=2——线程3直接跳过了next=1的轮次,少跑一次循环

实际运行时这类跳涨会频繁发生,绝大多数线程都不会走完next从0到4的全部5次迭代,总循环次数自然远小于25,且会因为线程调度的争抢顺序不同出现数值波动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:21:39