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率先执行,第一次exchange读到初始值0,写入自己的next=0,随后把自己的next更新为1
- 线程1还没跑下一轮,线程2启动执行,第一次exchange读到0,写入自己的初始next=0,随后把自己的next更新为1
- 线程1抢到执行权跑第二轮,exchange读到线程2写入的0,写入自己的next=1,随后把自己的next更新为2
- 刚启动的线程3抢到执行权,exchange读到线程1刚写入的1,写入自己的初始next=0,随后直接把自己的next更新为
max(1,0)+1=2——线程3直接跳过了next=1的轮次,少跑一次循环
实际运行时这类跳涨会频繁发生,绝大多数线程都不会走完next从0到4的全部5次迭代,总循环次数自然远小于25,且会因为线程调度的争抢顺序不同出现数值波动。
内容的提问来源于stack exchange,提问作者Peipei
相关产品推荐
相关产品推荐

