当然,当前的 C 和 C++ 标准没有提及这个主题。
据我所知,Posix 仍然避免正式定义并发模型(不过,我可能已经过时了,在这种情况下,我的答案仅适用于早期的 Posix 版本)。因此,必须带着一点同情心来阅读它所说的内容 - 它没有精确地列出该领域的要求,但实现者应该“知道它的含义”并做一些使线程可用的事情。
当标准说互斥体“同步内存访问”时,实现必须假设这意味着在一个线程的锁下所做的更改将在其他线程的锁下可见。换句话说,同步操作包括一种或另一种内存屏障是必要的(尽管还不够),并且内存屏障的必要行为是它必须假设全局变量可以更改。
线程不能作为库实现涵盖了 pthread 实际可用所需的一些特定问题,但在撰写本文时(2004 年)Posix 标准中尚未明确说明。在允许程序员“令人信服地推理程序正确性”方面,您的编译器编写者或为您的实现定义内存模型的任何人是否同意 Boehm 的“可用”含义变得非常重要。
请注意,Posix 不保证一致的内存缓存,因此如果您的实现反常想要缓存do_something
在你的代码的寄存器中,然后即使您将其标记为易失性,它可能会反常地选择在同步操作和读取之间不弄脏 CPU 的本地缓存do_something
。因此,如果编写器线程在具有自己的缓存的不同 CPU 上运行,即使这样您也可能看不到更改。
这就是线程不能仅作为库实现的(原因之一)。这种仅从本地 CPU 缓存获取易失性全局变量的优化在单线程 C 实现中有效[*],但会破坏多线程代码。因此,编译器需要“了解”线程,以及它们如何影响其他语言功能(例如 pthreads 之外的示例:在 Windows 上,缓存始终是一致的,Microsoft 阐明了它授予的附加语义volatile
在多线程代码中)。基本上,您必须假设,如果您的实现在提供 pthreads 函数方面遇到了麻烦,那么它也会在定义一个可行的内存模型(其中锁实际上同步内存访问)方面遇到麻烦。
如果编译器可以内联
函数并证明它不访问
do_shutdown,那么是否可以缓存
do_shutdown 即使在多线程中
环境?非内联的怎么样
在同一个编译单元中运行?
对所有这些都是肯定的 - 如果该对象是非易失性的,并且编译器可以证明该线程不会修改它(通过其名称或通过别名指针),并且如果没有发生内存障碍,那么它可以重用以前的值。当然,有时可能还会存在其他特定于实现的条件来阻止它。
[*] 前提是实现知道全局不位于某个“特殊”硬件地址,这要求读取始终通过缓存到达主内存,以便查看影响该地址的任何硬件操作的结果。但是要将全局变量放在任何此类位置,或者通过 DMA 或其他方式使其位置特殊,需要特定于实现的魔法。如果没有任何这样的魔法,原则上的实现有时可以知道这一点。