multithreading - 使用互斥锁的原子性 VS 同步
问题描述
int a = 0;
假设我们有 2 个线程访问共享内存a
;
Atomicity
确保每个操作都fully done
针对每个其他线程。因此,如果我让a = 5
每个线程都看到0
或5
(并且没有另一半更新 32 位值)。
现在这是我的困惑。如果我想确保每个其他线程只5
在上述分配之后看到。最流行的方法是使用locks
我是否正确?
如果是这样,那么locks
不提供原子性?原子性和同步是两个不同的概念?
解决方案
您实际描述的是另一个概念:可见性。
分配a=5
时,其他 CPU 需要一段时间才能看到此分配。如果您希望分配线程在所有其他线程都看不到a == 0
并且他们只能看到之前不继续执行a == 5
,那么您需要一个内存屏障。
因此,整理这些概念:
原子性
原子性是保证没有其他线程看到部分或暂时的状态变化,它们只看到一致的状态。
a == 5
在 x86(以及一些但不是全部的其他体系结构)上, CPU 保证诸如此类的分配是原子的。
但是,如果您有一个带有 state 的结构{ a: 5, b: 10 }
,并且该结构的不变量是 that b == a * 2
,为了更改状态,您需要 2 个分配,这不是原子的。在这种情况下,要使状态更改原子化,您需要锁。
锁具
锁允许您在线程之间实现一个协议,以便它们等到状态一致后再访问它。
在上面的示例中,实现它的方式是lock(mystruct); mystruct.a = myarg; mystruct.b = myarg * 2; unlock(mystruct);
. 的目标lock(mystruct)
是让线程等待直到状态mystruct
一致,从而手动实现原子性。
能见度
CPU缓存了很多东西。如果每次分配变量时 CPU 都必须写入主存,那么它至少会慢数千倍。
此外,CPU对指令重新排序以获得最佳执行速度。
所以分配最终会对其他线程可见,但不是立即的,也不是按顺序的。
如果你想要更强的保证,你需要一个内存屏障。
内存屏障
看下面的代码:
lock(mystruct);
mystruct.a = 9;
mystruct.b = 18;
unlock(mystruct);
// some thread might interleave here
lock(mystruct);
print(mystruct.a);
print(mystruct.b);
unlock(mystruct);
CPU 可能会说:“好吧,我只是设置了mystruct.a == 9
,所以我可以打印 9,我不需要mystruct.a
从主存储器中读取”
为了防止这种情况,实现unlock(mystruct)
通常包含一个内存屏障。
内存屏障阻止 CPU 假设在屏障之前发生的任何事情在屏障之后仍然有效,因此当它需要打印时,struct.a
它会从主内存中获取它。
把它绑在一起
由于 的实现unlock()
有一个内存屏障,锁不仅自动保证了原子性,还保证了正确的可见性。
推荐阅读
- ios - 如何在几秒钟后从其出现的 ios 中隐藏图像
- c++ - 为什么输出总是空的?
- reactjs - 我们如何在基于类的组件的 componentDidUpdate() 生命周期中停止重复调用,就像我们在 useEffect 挂钩中所做的那样?
- django - Django 会话认证和 Axios POST 信号
- php - Laravel - 返回控制器
- java - 我的变量没有显示有什么原因吗?- HTML
- reactjs - SetState 不适用于来自服务器的数据
- cross-validation - 我需要在 GridSearchCV 之后执行 cross_validation 吗?
- r - 如何从 R 中使用(新的)LinkedIn API?
- java - 从包含特殊字符的字符串中获取子字符串