c++11 及更高版本中 mutex.lock() 和 .unlock() 的确切线程间重新排序约束是什么?

2024-01-27

根据https://en.cppreference.com/w/cpp/atomic/memory_order https://en.cppreference.com/w/cpp/atomic/memory_order mutex.lock() and mutex.unlock()分别是获取和释放操作。获取操作使得不可能对其前面的后续指令重新排序。并且释放操作使得不可能在其之后重新排序较早的指令。这使得以下代码:

[Thread 1]
mutex1.lock();
mutex1.unlock();
mutex2.lock();
mutex2.unlock();
[Thread 2]
mutex2.lock();
mutex2.unlock();
mutex1.lock();
mutex1.unlock();

可以重新排序为以下(可能死锁)代码:

[Thread 1]
mutex1.lock();
mutex2.lock();
mutex1.unlock();
mutex2.unlock();
[Thread 2]
mutex2.lock();
mutex1.lock();
mutex2.unlock();
mutex1.unlock();

是否有可能发生这种重新排序。或者有什么规定可以阻止吗?


几乎重复:C++ 标准如何使用 memory_order_acquire 和 memory_order_release 防止自旋锁互斥锁中的死锁? https://stackoverflow.com/questions/61299704/how-c-standard-prevents-deadlock-in-spinlock-mutex-with-memory-order-acquire-a- 那是用手卷的std::atomic自旋锁,但同样的推理也适用:

编译器无法以可能导致死锁的方式对互斥体获取和释放进行编译时重新排序,而 C++ 抽象机没有死锁。这将违反假定规则。
它实际上会在源没有的地方引入无限循环,违反了这一规则:

ISO C++ 当前草案,第 6.9.2.3 节向前进展

18. https://eel.is/c++draft/intro.progress#18实现应该确保原子分配的最后一个值(按修改顺序)或同步操作会变成在有限时间内对所有其他线程可见.


ISO C++ 标准不区分编译时重新排序和运行时重新排序。事实上它并没有说什么re订购。它只说明了由于同步效果、每个原子对象的修改顺序的存在以及 seq_cst 操作的总顺序而保证您何时看到某些内容。将标准视为允许将事物以某种方式钉入汇编中是对标准的误读requires互斥体的获取顺序与源顺序不同。

获取互斥体本质上相当于原子 RMWmemory_order_acquire在互斥对象上。 (事实上​​,ISO C++ 标准甚至将它们分组在上面引用的 6.9.2.3 :: 18 中。)

您可以看到早期版本或宽松存储甚至 RMW 出现在互斥锁/解锁关键部分内而不是之前。但该标准要求原子存储(或同步操作)对其他线程及时可见,因此编译时重新排序force等到获取锁之后才进行操作可能会违反及时性保证。因此,即使是轻松的存储也无法使用以下命令进行编译时/源代码级重新排序mutex.lock(),仅作为运行时效果。

同样的推理也适用于mutex2.lock()。你是allowed查看重新排序,但编译器无法创建代码的情况requires如果重新排序使得执行在任何重要/长期可观察的方面与 C++ 抽象机不同,那么这种重新排序总是会发生。 (例如,围绕无限等待重新排序)。制造死锁算作其中一种方式,无论是出于这个原因还是其他原因。 (每个理智的编译器开发人员都会同意这一点,即使 C++ 没有正式语言来禁止它。)

注意互斥体unlock无法阻止,因此因此不会禁止两个解锁的编译时重新排序。 (如果中间没有缓慢或可能阻塞的操作)。但互斥锁解锁是一个“释放”操作,因此排除了这种情况:两个释放存储不能相互重新排序。


顺便说一句,防止 mutex.lock() 操作在编译时重新排序的实用机制只是使它们成为编译器不知道如何内联的常规函数​​调用。它必须假设函数不是“纯粹的”,即它们对全局状态有副作用,因此顺序可能很重要。这与将操作保留在关键部分内的机制相同:互斥锁和解锁函数如何防止 CPU 重新排序? https://stackoverflow.com/questions/50951011/how-does-a-mutex-lock-and-unlock-functions-prevents-cpu-reordering

用 std::atomic 编写的内联 std::mutexwould最终取决于编译器实际应用的规则,使操作立即可见,并且不会通过在编译时重新排序而引入死锁。如中所述C++ 标准如何使用 memory_order_acquire 和 memory_order_release 防止自旋锁互斥锁中的死锁? https://stackoverflow.com/questions/61299704/how-c-standard-prevents-deadlock-in-spinlock-mutex-with-memory-order-acquire-a

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++11 及更高版本中 mutex.lock() 和 .unlock() 的确切线程间重新排序约束是什么? 的相关文章

随机推荐