添加SetThreadAffinityMask()后Peterson算法测试代码陷入无限循环
关于Peterson算法设置线程亲和性后无限循环的问题分析与解决
我之前踩过一模一样的坑!咱们来一步步拆解这个问题:
核心原因:内存可见性与CPU缓存的“反向坑”
Peterson算法的正确性完全依赖于内存可见性——也就是一个线程对共享变量(比如flag数组、turn变量)的修改,另一个线程必须能立刻读取到最新值。
你可能会疑惑:绑到同一个核心不是应该更稳定吗?反而出问题?这是因为:
- 多核场景下,CPU的缓存一致性协议(比如MESI)会自动同步不同核心的缓存,所以共享变量的修改能及时被其他线程看到;
- 但当两个线程绑到同一个核心后,CPU会把共享变量缓存到L1/L2缓存里,线程切换时如果没有强制刷新缓存,两个线程就会各自读取到缓存里的旧值,导致Peterson的判断逻辑失效,直接陷入无限循环。
(老师说的“多核可能引发bug”其实是担心指令重排序,但你这里刚好是绑核后触发了缓存可见性的问题)
具体解决步骤
1. 给共享变量添加内存可见性保证
这是最关键的一步,必须确保共享变量的读写不被编译器或CPU缓存优化:
- 如果是C/C++,把
flag和turn声明为volatile,或者用std::atomic类型(更推荐原子类型,规范且安全):
这样编译器会禁止对这些变量做缓存优化,每次读写都直接操作主存,保证线程间的可见性。// 用volatile的写法 volatile bool flag[2] = {false, false}; volatile int turn = 0; // 更规范的原子类型写法 std::atomic<bool> flag[2] = {false, false}; std::atomic<int> turn = 0;
2. 检查SetThreadAffinityMask的调用是否正确
确保两个线程确实被绑定到同一个核心,比如绑到第0个核心的写法:
DWORD_PTR core_mask = 1 << 0; // 第0个核心的掩码 SetThreadAffinityMask(thread1_handle, core_mask); SetThreadAffinityMask(thread2_handle, core_mask);
如果不小心绑到了不同核心,虽然不会直接导致无限循环,但也可能引发其他问题,先确认这一步没问题。
3. 核对Peterson算法的逻辑正确性
再仔细检查你的算法实现,比如进入临界区的判断逻辑:
// 线程0的代码 flag[0] = true; turn = 1; // 等待线程1退出临界区,或者轮到自己 while (flag[1] && turn == 1); // 临界区操作... flag[0] = false; // 退出临界区
// 线程1的代码 flag[1] = true; turn = 0; while (flag[0] && turn == 0); // 临界区操作... flag[1] = false;
如果这里的turn赋值或者循环条件写错了,也会导致无限循环,绑核只是让这个问题暴露出来而已。
总结
绑核后出现无限循环的本质是Peterson算法依赖的内存可见性没有被保证,单个核心的缓存不会像多核那样自动同步。给共享变量加上volatile或原子类型,就能解决这个问题。
内容的提问来源于stack exchange,提问作者Lin
相关产品推荐
相关产品推荐

