同一互斥锁顺序上的锁定和解锁是否一致?

Are lock and unlock on the same mutex sequential consistent?

本文关键字:解锁 是否 锁定 顺序      更新时间:2023-10-16

对于互斥lock(),标准提到:

同一互斥锁上的先前 unlock(( 操作与此操作同步(如 std::memory_order 中定义(。

这个答案试图根据标准解释synchronize-with的含义。但是,看起来定义没有明确指定。

我的主要问题是,我能得到这个输出吗:

x: 1
y: 2

由于线程 A 中的内存重新排序,以下代码?如果BA解锁后锁定,是否保证Ax中的写入被B观察到?

std::mutex mutex;
int x = 0, y = 0;
int main() {
std::thread A{[] {
x = 1;
std::lock_guard<std::mutex> lg(std::mutex);
y = 0;
}};
std::thread B{[] {
std::lock_guard<std::mutex> lg(std::mutex);
y = x + 2;
}};
A.join();
B.join();
std::cout << "x: " << x << std::endl;
std::cout << "y: " << y << std::endl;
}

如果没有,基于标准的哪个部分? 换句话说,我们可以假设锁定/解锁之间存在顺序一致性吗?

我也看到了这个相关的问题,但它是针对单独的互斥体的。

与同步关系已明确定义。该标准规定如下:

某些库调用

与另一个线程执行的其他库调用同步。例如,原子存储发布与从存储中获取其值的加载获取同步。[...][注意:同步操作的规范定义一个操作何时读取另一个操作写入的值。对于原子对象,定义是明确的。给定互斥锁上的所有操作都发生在一个总订单中。每次互斥锁获取都会"读取"上一个互斥锁版本写入的值。— 尾注]

并进一步:

对原子对象 M 执行释放操作的原子操作A与对M执行获取操作并从释放中的任何副作用中获取其值的原子操作B同步 以A为首的序列。

换句话说,如果获取操作 A "看到">发布操作 B 存储的值,则AB同步。

考虑一个自旋锁,你只需要一个原子布尔标志。所有操作都在该标志上运行。为了获取锁,您已经使用原子读取-修改-写入操作设置了标志。对原子对象的所有修改都完全按修改顺序排序,并且可以保证 RMW 操作始终读取与该 RMW 操作关联的写入之前写入的最后一个值(按修改顺序(。

由于这种保证,对锁定/解锁操作使用获取/释放语义就足够了,因为成功的锁定操作始终"看到"上一个解锁写入的值。

关于您的问题:

如果BA解锁后锁定,B是否保证遵守xA写入?

重要的部分是"如果B解锁后A锁定"!如果这是有保证的,那么是的,B的锁定操作与A的解锁同步,从而建立发生之前的关系。因此B将观察A的写作。但是,您的代码不能保证BA后锁定,因此您有一个潜在的数据争用,这将导致@ReinstateMonica正确指出的未定义行为。

更新
对 x 的写入是在A解锁之前排序的。操作是否在互斥锁之外(之前(并不重要。事实上,从理论上讲,编译器可以对操作进行重新排序,使其最终进入互斥锁(尽管这不太可能(。之前排序也是发生前定义的一部分,因此我们有以下内容:

std::thread A{[] {
x = 1; // a
std::lock_guard<std::mutex> lg(std::mutex);
y = 0;
// implicit unlock: b
}};
std::thread B{[] {
std::lock_guard<std::mutex> lg(std::mutex); // c
y = x + 2;
}};

假设BA解锁后锁定,我们有:

A 在 B 之前
  • 排序 ->A发生在B之前
  • b
  • 与 c 同步 ->b发生在c之前

由于发生前关系是传递的,因此在c之前发生。所以是的,对于在A解锁之前排序的所有操作都是如此 - 无论它们是否在锁内。