Wad*_*Wad 8 multithreading caching multicore
我正在阅读关于使用bool进行线程控制的问题,并且被@eran的这个答案所吸引:
使用volatile仅在单核上足够,其中所有线程使用相同的缓存.在多核上,如果在一个核上调用stop()而在另一个核上执行run(),则CPU缓存可能需要一些时间来进行同步,这意味着两个核可能会看到两个不同的isRunning_视图.
如果您使用同步机制,它们将确保所有缓存获得相同的值,代价是暂停程序一段时间.性能或正确性对您来说更重要取决于您的实际需求.
我花了一个多小时搜索一些声明,说同步原语强制缓存一致但失败了.我最接近的是维基百科:
关键字volatile不保证内存屏障可以强制执行缓存一致性.
这表明内存屏障确实强制缓存一致性,并且由于一些同步原语是使用内存屏障(同样来自维基百科)实现的,这是一些"证据".
但我不知道是否相信这一点与否,并确保我不会误解它.
有人可以澄清一下吗?
据我所知,同步原语根本不会影响缓存一致性.缓存是隐藏的法语,它不应该对用户可见.缓存一致性协议应该在没有程序员参与的情况下工作.
同步原语将影响内存排序,这是通过处理器的ISA很好地定义并且对用户可见.
具有详细信息的良好来源是计算机体系结构综合讲座中的内存一致性和缓存一致性入门.
编辑:澄清你的疑问
维基百科的说法有点不对劲.我认为混淆可能来自内存一致性和缓存一致性这两个术语.它们并不代表同一件事.
volatileC中的关键字意味着变量总是从内存中读取(而不是寄存器),并且编译器不会重新排序它周围的加载/存储.这并不意味着硬件不会重新排序加载/存储.这是内存一致性问题.当使用较弱的一致性模型时,程序员需要使用同步原语来强制执行特定的排序.这与缓存一致性不同.例如,如果线程1修改位置A,则在此事件之后线程2加载位置A,它将接收更新的(一致的)值.如果使用缓存一致性,这应该自动发生.内存排序是一个不同的问题.您可以查看着名的论文共享内存一致性模型:教程以获取更多信息.其中一个比较着名的例子是Dekker算法,它需要顺序一致性或同步原语.
EDIT2:我想澄清一件事.虽然我的缓存一致性示例是正确的,但是存在内存一致性似乎与其重叠的情况.存储在处理器中执行但延迟到缓存(它们位于存储队列/缓冲区中)时.由于处理器的缓存未收到更新的值,因此其他缓存也不会.这可能看起来像缓存一致性问题,但实际上它不是,实际上是ISA的内存一致性模型的一部分.在这种情况下,可以使用同步原语将存储队列刷新到缓存.考虑到这一点,以粗体突出显示的维基百科文本是正确的,但另一个仍然有点错误:关键字volatile不保证内存屏障可以强制执行缓存一致性.它应该说:关键字volatile不保证内存屏障来强制执行内存一致性.
简短答案:缓存一致性在大多数情况下都有效,但并非总是如此。您仍然可以读取过时的数据。如果您不想冒险,那就使用内存屏障
长答案:CPU内核不再直接连接到主存储器。所有加载和存储都必须经过缓存。每个CPU都有自己的专用缓存这一事实引起了新的问题。如果有多个CPU正在访问同一内存,则仍必须确保两个处理器始终都能看到相同的内存内容。如果一个处理器上的高速缓存行脏了(即尚未将其写回到主存储器),而第二个处理器试图读取相同的存储器位置,则读取操作不能仅发送到主存储器。。而是需要第一个处理器的缓存行的内容。现在的问题是,何时必须进行此缓存行传输?这个问题很容易回答:当一个处理器需要高速缓存行时,该行在另一处理器的高速缓存中脏了以进行读取或写入。但是,一个处理器如何确定另一个处理器的缓存中的缓存行是否脏了?假设仅由于高速缓存行由另一个处理器加载而已(不是最佳选择)。通常,大多数内存访问是读取访问,并且生成的高速缓存行不会变脏。这是缓存一致性协议。CPU通过MESI或其他某种缓存一致性协议在其缓存之间保持数据一致性。
在具有缓存一致性的情况下,即使由另一个CPU修改了缓存行,我们也不应总是看到该缓存行的最新值吗?毕竟,这是缓存一致性协议的全部目的。通常,当修改缓存线时,相应的CPU向所有其他CPU发送“无效缓存线”请求。事实证明,CPU可以立即向无效请求发送确认,但将高速缓存行的实际无效推迟到以后的某个时间点。这是通过无效队列完成的。现在,如果我们很不幸在这个短窗口内(在CPU确认无效请求与实际使高速缓存行无效之间)读取高速缓存行,那么我们可以读取一个过时的值。现在,为什么CPU会做如此可怕的事情。简单的答案就是性能。
Scenario 1 : CPU1 receives an invalidation request from CPU2. CPU1 also has a lot of stores and loads queued up for the cache. This means that the invalidation of the requested cacheline takes times and CPU2 gets stalled waiting for the acknowledgment
Scenario 2 : CPU1 receives a lot of invalidation requests in a short amount of time. Now it takes time for CPU1 to invalidate all the cachelines.
Run Code Online (Sandbox Code Playgroud)
实际上,将条目放置到无效队列中是CPU承诺在传输有关该高速缓存行的任何MESI协议消息之前处理该条目。因此,无效队列是即使简单读取单个变量也可能看不到最新值的原因。
现在敏锐的读者可能会想,当CPU要读取高速缓存行时,它可以先扫描失效队列,然后再从高速缓存中读取。这应该避免该问题。但是,CPU和失效队列实际上位于高速缓存的相对侧,这限制了CPU直接访问失效队列。(一个CPU的缓存的无效队列由来自其他CPU的缓存一致性消息通过系统总线填充。因此,将无效队列放置在缓存和系统总线之间是有意义的。)因此,为了实际看到任何共享变量的最新值,我们应该清空失效队列。通常,读取内存屏障可以做到这一点。
我刚刚谈到了无效队列和读取内存障碍。[1]是了解读写内存屏障的需求以及MESI缓存一致性协议的详细信息的良好参考。
[1] http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf