互斥锁如何确保变量的值在内核之间保持一致?
How does a mutex ensure a variable's value is consistent across cores?
如果我有一个int,我想从一个线程写入并从另一个线程读取,我需要使用std::atomic
,以确保其值在核心之间是一致的,无论读取和写入它的指令是否在概念上是原子的。如果不这样做,则可能是读取核心在其缓存中有一个旧值,并且不会看到新值。这对我来说很有意义。
如果我有一些不能自动读写的复杂数据类型,我需要使用一些同步原语来保护对它的访问,比如std::mutex
。这将防止对象进入(或被读取)不一致的状态。这对我来说很有意义。
对我来说没有意义的是互斥锁如何帮助解决原子解决的缓存问题。它们的存在似乎仅仅是为了防止对某些资源的并发访问,而不是为了将该资源中包含的任何值传播到其他核心的缓存。它们的语义中是否有我遗漏的处理这个问题的部分?
正确的答案是魔法小精灵-例如,它只是工作。每个平台的std::atomic的实现必须做正确的事情。
正确的东西是三部分的组合。
首先,编译器需要知道它不能跨边界移动指令[事实上,在某些情况下它可以,但假设它不会]。
其次,缓存/内存子系统需要知道——这通常是使用内存屏障完成的,尽管x86/x64通常有如此强大的内存保证,在绝大多数情况下这是不必要的(这是一个很大的遗憾,因为错误的代码实际上是错误的)。
最后,CPU需要知道它不能重新排序指令。现代cpu在重新排序操作方面非常积极,并确保在单线程情况下这是不明显的。他们可能需要更多的提示,这在某些地方不会发生。
对于大多数cpu来说,第2部分和第3部分归结为同一件事——内存屏障意味着两者。第1部分完全在编译器内部,由编译器编写者来完成。
参见Herb suters的演讲"Atomic Weapons",了解更多有趣的信息。
内核间的一致性由内存屏障保证(这也可以防止指令重新排序)。当您使用std::atomic
时,不仅可以自动访问数据,而且编译器(和库)还可以插入相关的内存屏障。
互斥锁的工作方式相同:互斥锁实现(例如:
大多数现代多核处理器(包括x86和x64)都是缓存一致的。如果两个核心在缓存中持有相同的内存位置,其中一个更新了该值,则更改会自动传播到其他核心的缓存。它的效率很低(同时从两个内核写入同一条缓存线非常慢),但是没有缓存一致性,编写多线程软件将非常困难。
就像syam说的,内存屏障也是必需的。它们防止编译器或处理器重新排序内存访问,并且还强制写入内存(或至少写入缓存),例如,当由于编译器优化而将变量保存在寄存器中时。
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- int(c) 和 c-'0' 之间的区别。C++
- 在cuda线程之间共享大量常量数据
- 在c代码之间共享数据的最佳方式
- Mix_Init和Mix_OpenAudio SDL之间的区别是什么
- C++ 使用 assign 函数的字符串与直接使用 '=' 更改值的字符串之间的区别
- VSOMEIP-2个设备之间的通信(TCP/UDP)不工作
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 大小相等但成员数量不同的结构之间的性能差异
- OpenAcc标准中内核和并行指令之间的差异
- 使用用户模式和内核之间共享内存的慢速通信
- 如何在 CUDA 网格中所有线程之间共享的__global__内核中定义变量
- 如何在内核之间同步 TSC
- 如何在进程之间共享内核对象,例如可等待计时器
- 根据内核/掩码在相关的相邻像素之间有效地迭代
- 互斥锁如何确保变量的值在内核之间保持一致?
- 当结合BSP库和std::vector时,我无法在内核之间发送数据
- 在OpenCL 1.2内核之间传递变量/内核之间的通信
- 预测 2.6.16 和 2.6.26 内核版本之间的"kernel too old"错误
- 内核和驱动程序之间的区别是什么