基于布尔变量的简易mutex(互斥锁)lock/unlock实现存在哪些问题?
嗨,这个实现看起来是个简单的自旋锁思路,但除了忙等的性能问题外,还有几个完全破坏并发安全性的致命问题,根本不能在多线程场景下用:
竞态条件导致锁完全失效
你lock函数里的循环检查和赋值是两个分开的操作,不是原子的。举个场景:
线程A检查*m是false,退出循环,但还没来得及执行*m = true;的时候,线程B也同时检查到*m是false,也退出了循环。接下来两个线程都会把*m设为true,结果就是两个线程同时持有了锁——这直接违背了互斥锁的核心目的,临界区代码会被同时执行,数据肯定乱套。编译器/CPU优化导致的死循环
现代编译器和CPU会做各种优化来提升性能。比如在lock的while循环里,编译器可能会把*m的值缓存到线程的寄存器里,之后就不再从内存重新读取了。这就会出现:就算另一个线程执行了unlock把*m改成false,当前线程的寄存器里还是旧的true值,会无限卡在循环里,永远看不到锁被释放。
同样,unlock里的*m = false;可能只是写到了CPU的本地缓存,没有同步到主存,其他线程根本看不到这个更新,还是会一直认为锁被持有。缺乏内存屏障引发的指令重排
就算缓存的问题不存在,CPU可能会对指令顺序进行重排。比如unlock里的*m = false;可能被重排到临界区代码的后面执行——也就是说,线程已经“解锁”了,但临界区里的操作还没完成,其他线程就已经进入临界区,导致数据错乱。
本质上,这个实现完全没考虑多线程环境下的原子性、可见性、有序性这三个并发安全的核心要求。正确的自旋锁必须依赖CPU提供的原子操作(比如test-and-set这类指令),同时配合内存屏障来保证内存可见性和指令执行顺序。
举个简化的正确原子操作版本(伪代码):
void lock(boolean *m) { // 原子的test-and-set:检查当前值,同时设置为true,返回旧值 while (atomic_test_and_set(m) == true) {} } void unlock(boolean *m) { atomic_store(m, false); // 内存屏障确保其他线程能立刻看到这个更新 }
内容的提问来源于stack exchange,提问作者Hupfauer

