joh*_*dav 6 c++ multithreading cpu-cache c++11
我目前正在尝试学习C ++ 11线程API,并且发现各种资源没有提供必要的信息:如何处理CPU缓存。现代CPU的每个内核都有一个缓存(这意味着不同的线程可能使用不同的缓存)。这意味着一个线程有可能将一个值写入内存,而另一个线程可能看不到该值,即使它看到第一个线程也进行了其他更改。
当然,任何好的线程API都提供了解决此问题的方法。但是,在C ++的线程API中,尚不清楚它是如何工作的。我知道,std::mutex例如,a 以某种方式保护内存,但尚不清楚它的作用:是否清除了整个CPU缓存,是否仅清除了当前线程缓存中互斥对象内部访问的对象,或其他?
同样,显然,只读访问不需要互斥锁,但是如果线程1和仅线程1持续写入内存以修改对象,其他线程可能不会看到该对象的过时版本,从而使需要某种缓存清除吗?
原子类型是否只是绕过缓存并使用一条CPU指令从主存储器中读取值?他们是否保证访问内存中的其他位置?
在CPU缓存的上下文中,C ++ 11的线程化api中的内存访问如何工作?
有些问题,比如这一个讲内存栅栏,以及一个内存模式,但没有源似乎是在CPU缓存的情况下,这是什么这个问题问到解释这一点。
std::mutex具有release-acquire内存排序语义,因此线程 A 中happened-before从线程 A 的角度原子写入临界区的所有内容都必须对线程 B 可见,然后才能进入线程 B 中的临界区。
阅读http://en.cppreference.com/w/cpp/atomic/memory_order以开始使用。另一个很好的资源是C++ Concurrency in Action一书。话虽如此,当使用高级同步原语时,您应该能够忽略大多数这些细节,除非您很好奇或想弄脏自己的手。
我想我明白你的意思。这里有三件事在起作用。
\n\nC++11 标准描述了语言级别发生的情况...锁定 astd::mutex是同步操作。C++ 标准没有描述其工作原理。就 C++ 标准而言,CPU 缓存并不存在。
C++ 实现在某些时候会在应用程序中放入一些实现互斥锁的机器代码。创建此实现的工程师必须考虑 C++11 规范和体系结构规范。
CPU 本身管理缓存的方式是提供 C++ 实现工作所需的语义。
如果您查看原子,这可能会更容易理解,原子会转换为更小的汇编代码片段,但仍然提供同步。例如,在GodBolt上试试这个:
\n\n#include <atomic>\n\nstd::atomic<int> value;\n\nint acquire() {\n return value.store(std::memory_order_acquire);\n}\n\nvoid release() {\n value.store(0, std::memory_order_release);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n你可以看到装配:
\n\nacquire():\n mov eax, DWORD PTR value[rip]\n ret\nrelease():\n mov DWORD PTR value[rip], 0\n ret\nvalue:\n .zero 4\nRun Code Online (Sandbox Code Playgroud)\n\n因此,在 x86 上,\xe2\x80\x99s 没有任何必要,CPU 已经提供了所需的内存排序语义(尽管您可以使用mfence通常由操作隐含的显式 it\xe2\x80\x99s)。这绝对不是它在所有处理器上的工作方式,请参阅功率输出:
acquire():\n.LCF0:\n0: addis 2,12,.TOC.-.LCF0@ha\n addi 2,2,.TOC.-.LCF0@l\n addis 3,2,.LANCHOR0@toc@ha # gpr load fusion, type int\n lwz 3,.LANCHOR0@toc@l(3)\n cmpw 7,3,3\n bne- 7,$+4\n isync\n extsw 3,3\n blr\n .long 0\n .byte 0,9,0,0,0,0,0,0\nrelease():\n.LCF1:\n0: addis 2,12,.TOC.-.LCF1@ha\n addi 2,2,.TOC.-.LCF1@l\n lwsync\n li 9,0\n addis 10,2,.LANCHOR0@toc@ha\n stw 9,.LANCHOR0@toc@l(10)\n blr\n .long 0\n .byte 0,9,0,0,0,0,0,0\nvalue:\n .zero 4\nRun Code Online (Sandbox Code Playgroud)\n\n这里有明确的isync指令,因为如果没有它们,Power 内存模型提供的保证会更少。
然而,这只是将事情降低到一个较低的水平。CPU 本身使用MESI 协议等技术来管理共享高速缓存,这是一种维护高速缓存一致性的技术。
\n\n在 MESI 协议中,当核心修改缓存块时,它必须从其他缓存中刷新该块。其他内核将块标记为无效,并在必要时将内容写入主内存。这是低效的,但却是必要的。因此,您不想尝试将一堆常用的互斥体或原子变量放入一小块内存区域中,因为最终可能会导致多个核心争夺同一块缓存。维基百科的文章相当全面,并且比我在这里写的更详细。
\n\n我忽略的事实是,互斥体通常需要某种内核级支持才能使线程进入睡眠或唤醒状态。
\n