为何std::atomic<int>++性能远慢于std::mutex保护的int++?
原子变量与互斥锁保护普通变量的性能对比问题
测试程序
为对比std::atomic<int>++与std::mutex保护的int++性能差异,编写了如下测试代码:
#include <iostream> #include <atomic> #include <mutex> #include <thread> #include <chrono> #include <limits> using namespace std; #ifndef INT_MAX const int INT_MAX = numeric_limits<std::int32_t>::max(); const int INT_MIN = numeric_limits<std::int32_t>::min(); #endif using std::chrono::steady_clock; const size_t LOOP_COUNT = 12500000; const size_t THREAD_COUNT = 8; int intArray[2] = { 0, INT_MAX }; atomic<int> atomicArray[2]; void atomic_tf() {//3.19s for (size_t i = 0; i < LOOP_COUNT; ++i) { atomicArray[0]++; atomicArray[1]--; } } mutex m; void mutex_tf() {//0.25s m.lock(); for (size_t i = 0; i < LOOP_COUNT; ++i) { intArray[0]++; intArray[1]--; } m.unlock(); } int main() { { atomicArray[0] = 0; atomicArray[1] = INT_MAX; thread tp[THREAD_COUNT]; steady_clock::time_point t1 = steady_clock::now(); for (size_t t = 0; t < THREAD_COUNT; ++t) { tp[t] = thread(atomic_tf); } for (size_t t = 0; t < THREAD_COUNT; ++t) { tp[t].join(); } steady_clock::time_point t2 = steady_clock::now(); cout << (float)((t2 - t1).count()) / 1000000000 << endl; } { thread tp[THREAD_COUNT]; steady_clock::time_point t1 = steady_clock::now(); for (size_t t = 0; t < THREAD_COUNT; ++t) { tp[t] = thread(mutex_tf); } for (size_t t = 0; t < THREAD_COUNT; ++t) { tp[t].join(); } steady_clock::time_point t2 = steady_clock::now(); cout << (float)((t2 - t1).count()) / 1000000000 << endl; } return 0; }
测试结果
在Windows和Linux平台(分别使用clang14、g12编译)多次运行,结果一致:
atomic_tf函数执行耗时3秒以上mutex_tf函数执行耗时0.25秒以上
两者性能差距近10倍。
问题与解答
测试程序是否能说明原子变量开销远大于互斥锁+普通变量?
不能直接得出这个结论,该测试是极端高竞争的批量操作场景,不代表所有使用场景。如果是单次或低频率的并发操作,原子变量通常比互斥锁更高效——因为互斥锁涉及内核态切换的开销,而原子变量多数情况下是用户态的无锁操作。
性能差异的成因
锁竞争次数的本质差异
mutex_tf中,每个线程仅在进入时做一次锁竞争,拿到锁后所有循环内的++/--都是普通内存操作,完全无并发竞争,操作直接在本地缓存完成,速度极快。atomic_tf中,循环内的每一次++/--都是原子操作,每次都要和其他7个线程竞争缓存一致性:CPU需要通过MESI协议同步多个核心的缓存状态,每次原子操作都要经历“缓存失效-获取最新值-修改-同步回缓存”的过程,这个开销会随竞争线程数增加急剧放大。
操作优化空间的差异
- 互斥锁锁定后,线程对普通变量的操作是无同步要求的单线程操作,编译器能做循环展开、寄存器缓存变量等激进优化。
- 原子变量的操作强制要求内存可见性和原子性,编译器无法对这类操作做优化,且每次操作都要触发缓存同步的硬件开销。
内容的提问来源于stack exchange,提问作者gongliming7
相关产品推荐
相关产品推荐

