为何C++程序中使用std::lock_guard后运行时间反而更短?
为什么带
std::lock_guard的多线程代码比无锁版本更快? 现象回顾
你的测试中,带锁版本耗时5.7s且结果正确(2000000000),无锁版本耗时10.7s且结果错误(1010269798),这和“锁会增加额外开销”的直觉认知相反,核心原因是无锁版本的严重缓存行颠簸抵消了并行收益,甚至比串行执行更慢。
核心原因分析
1. 无锁版本的致命问题:缓存行颠簸
a++并非原子操作,实际拆解为三个步骤:
- 从缓存/内存加载
a的值到CPU寄存器 - 寄存器中的值执行+1操作
- 将新值写回缓存/内存
当两个线程同时操作同一个int a时,受CPU缓存一致性协议(如MESI)约束:
- 线程1修改
a后,线程2缓存中a所在的缓存行会被标记为无效 - 线程2必须重新从主内存加载
a的最新值,这个操作的延迟远高于CPU内部运算 - 这种“修改-失效-重新加载”的循环会在两个线程间反复触发,导致绝大多数CPU时间浪费在缓存同步上,而非实际的自增计算
这种现象就是缓存行颠簸(Cache Line Thrashing),它的开销远超过锁的开销,最终导致无锁版本运行更慢,同时因数据竞争破坏了自增操作的原子性,出现计数丢失。
2. 带锁版本的串行执行反而更高效
std::lock_guard保证同一时间只有一个线程能执行add()内的循环:
- 第一个线程完整执行1e9次自增,期间
a始终在该线程的缓存中,不会触发缓存失效 - 第一个线程执行完毕释放锁后,第二个线程才开始执行,同样全程复用自身缓存
- 锁本身的开销和缓存行颠簸的巨大开销相比可以忽略,因此总耗时更短,同时保证了结果的正确性
正确的并行优化方式
如果想真正利用多线程提升效率,应避免多个线程竞争同一个变量,比如让每个线程操作独立的局部变量,最后再汇总结果:
#include <iostream> #include <thread> #include <ctime> using namespace std; class test { public: void add(int& local_a) { for(int i = 0; i < 1e9; i++) { local_a++; } } }; int main() { test t; int a1 = 0, a2 = 0; auto start = clock(); std::thread t1(&test::add, &t, ref(a1)); std::thread t2(&test::add, &t, ref(a2)); t1.join(); t2.join(); auto end = clock(); cout << "total = " << a1 + a2 << endl; cout << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl; return 0; }
这个版本中两个线程各自操作独立变量,不会触发缓存行颠簸,能真正发挥多线程的并行优势,耗时会比带锁版本更短。
内容的提问来源于stack exchange,提问作者IceCoconut
相关产品推荐
相关产品推荐

