硬件已实现缓存一致性(如MESI),为何仍会出现竞态条件?
问题分析与解答
问题代码
#include <iostream> #include <thread> #include <vector> #include <chrono> #include <mutex> using namespace std::chrono; const int nthreads = 4; const int64_t ndata = 1600000; int total_sum = 0; void compute_sum(int tid, std::vector<int>& d) { int st = (ndata / nthreads) * tid; int en = (ndata / nthreads) * (tid + 1); for (int i = st; i < en; i++) { total_sum += d[i]; } } int main(int argc, char ** arg) { std::vector<std::thread> threads; std::vector<int> data; for (int i = 0; i < ndata; i++) { data.push_back(i); } for (int i = 0; i < nthreads; i++) { threads.push_back(std::thread(compute_sum, i, std::ref(data))); } for (auto &th : threads) { th.join(); } return 0; }
为什么MESI协议无法避免竞态条件?
MESI这类缓存一致性协议解决的是缓存间的数据可见性问题:它保证当一个CPU核心修改了缓存中的共享数据后,其他核心的缓存会同步更新该数据,确保所有核心看到的共享数据是一致的。但它管不了指令执行的原子性——这才是竞态条件的根源。
看代码里的total_sum += d[i],这个操作拆解成CPU指令是三步独立操作:
- 从缓存/内存读取
total_sum的值到寄存器 - 把寄存器里的值加上
d[i] - 把新值写回缓存/内存
这三步之间存在执行空隙,很容易被其他线程的指令插入。比如:线程A读取total_sum=10后,还没完成加值和写回,线程B也读取了total_sum=10;接着线程A完成加5操作,写回total_sum=15;最后线程B完成加3操作,写回total_sum=13——直接覆盖了线程A的修改,最终结果少加了5。
MESI只能保证:如果线程B是在线程A写回之后读取total_sum,那它拿到的是最新的15;但如果线程B是在A写回之前读的,它拿到的还是旧值,MESI无法阻止这种“指令交错执行”的情况。要解决竞态条件,必须用原子操作(比如std::atomic<int>)把这三步打包成一个不可分割的原子指令,或者用锁保证同一时间只有一个线程执行该操作。
内容的提问来源于stack exchange,提问作者Suriyaa MM
相关产品推荐
相关产品推荐

