除了最简单的自旋锁算法外,互斥代码相当复杂:一个好的优化的互斥锁/解锁代码包含了即使是优秀的程序员也难以理解的代码。它使用特殊的比较和设置指令,不仅管理解锁/锁定状态,还管理等待队列,可选择使用系统调用进入等待状态(用于锁定)或唤醒其他线程(用于解锁)。
无论如何,一般的编译器都无法解码和“理解”所有复杂的代码(同样,除了简单的自旋锁),所以即使对于不知道互斥锁是什么的编译器,它与同步的关系如何,实际上编译器无法围绕此类代码优化任何内容。
如果代码是“内联”的,或者可用于跨模块优化的分析,或者全局优化可用。
我认为编译器实际上并不理解
pthread_mutex_lock() 是一个特殊的函数,所以我们只是被保护了
按序列点?
编译器不知道它做了什么,所以不会尝试围绕它进行优化。
它有多“特别”?它是不透明的并被这样对待。 在不透明函数中并不特殊。
与可以访问任何其他对象的任意不透明函数没有语义差异。
我关心的是缓存。编译器可以放置 _protected 的副本吗
在堆栈或寄存器中,并在
任务?
是的,在代码中,通过以编译器可以遵循的方式使用变量名或指针透明和直接地作用于对象。不在可能使用任意指针间接使用变量的代码中。
所以是的在对不透明函数的调用之间。没有跨越。
还有只能在函数中使用的变量,按名称:对于既没有获取地址也没有绑定引用的局部变量(这样编译器无法遵循所有进一步的用途)。这些确实可以跨任意调用“缓存”,包括锁定/解锁。
如果不是,是什么阻止了这种情况的发生?是这个的变体
模式易受攻击?
函数的不透明度。非内联。汇编代码。系统调用。代码复杂度。让编译器摆脱困境并认为“这是复杂的东西只需调用它”的一切。
编译器的默认位置始终是“让我们愚蠢地执行我不明白正在做什么”而不是“我会优化它/让我们重写我更了解的算法”。大多数代码没有以复杂的非本地方式进行优化。
现在让我们假设绝对更糟(从编译器应该放弃的角度来看,从优化算法的角度来看这是绝对最好的):
- 函数是“内联”(= 可用于内联)(或全局优化启动,或所有函数在道德上都是“内联”);
-
在同步原语(锁定或解锁)中不需要内存屏障(如在单处理器时间共享系统和多处理器强有序系统中),因此它不包含此类内容;
-
没有使用特殊指令(如比较和设置)(例如对于自旋锁,解锁操作是简单的写入);
-
没有系统调用来暂停或唤醒线程(在自旋锁中不需要);
那么我们可能会遇到问题,因为编译器可以围绕函数调用进行优化。 这可以通过插入一个编译器屏障(例如一个带有“clobber”的其他可访问变量的空 asm 语句)来轻松解决。这意味着编译器只是假设被调用函数可能访问的任何内容都是“重创”。
或者受保护的变量是否需要是可变的。
您可以将其设置为 volatile 的原因通常是为了让事情变得 volatile:确保能够在调试器中访问变量,防止浮点变量在运行时具有错误的数据类型,等等。
使其 volatile 实际上甚至无法解决上述问题,因为 volatile 本质上是抽象机器中的内存操作,具有 I/O 操作的语义,因此仅按以下顺序排序尊重
- 像 iostream 这样的真实 I/O
- 系统调用
- 其他不稳定操作
- asm 内存破坏器(但随后不会围绕这些重新排序内存副作用)
- 调用外部函数(因为它们可能会执行上述操作)
对于非易失性内存副作用而言,Volatile 没有顺序。这使得 volatile实际上对于编写线程安全代码毫无用处(对于实际用途而言),即使是最volatile 先验有帮助的特定情况,不需要内存围栏的情况:在单个 CPU 上的时间共享系统上编程线程原语时。 (这可能是 C 或 C++ 中最不被理解的方面之一。)
因此,尽管 volatile 确实可以防止“缓存”,除非所有共享变量都是 volatile,否则 volatile 甚至不会阻止编译器重新排序锁定/解锁操作。