同一互斥体上的锁定和解锁顺序是否一致?
Are lock and unlock on the same mutex sequential consistent?
对于互斥lock()
,标准mentions:
Prior unlock() operations on the same mutex synchronize-with (as defined in std::memory_order) this operation.
这个answer试图按照标准来解释synchronize-with
是什么意思。不过,貌似定义没有明确说明。
我的主要问题是,我能得到这个输出吗:
x: 1
y: 2
由于线程 A 中的内存重新排序,以下代码?如果 A
解锁后 B
锁定,A
中的 x
上的写入是否保证被 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;
}
如果不是,基于标准的哪一部分?
换句话说,我们可以假设 lock/unlock?
之间存在顺序一致性吗?
我也看到了这个 问题,但它是针对单独的互斥体的。
明确定义了同步关系。该标准规定如下:
Certain library calls synchronize with other library calls performed by another thread. For example, an atomic store-release synchronizes with a load-acquire that takes its value from the store. [...] [ Note: The specifications of the synchronization operations define when one reads the value written by another. For atomic objects, the definition is clear. All operations on a given mutex occur in a single total order. Each mutex acquisition "reads the value written" by the last mutex release. — end note ]
还有:
An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and takes its value from any side effect in the release
sequence headed by A.
所以换句话说,如果一个acquire操作A "sees"一个release操作存储的值B,那么A 与 B.
同步
考虑一个自旋锁,您只需要一个原子 bool 标志。所有操作都在该标志上进行。为了获得锁,您已经使用原子读-修改-写操作设置了标志。原子对象上的所有修改完全按修改顺序排序,并且保证 RMW 操作始终读取在与该 RMW 操作关联的写入之前写入的最后一个值(按修改顺序)。
由于这种保证,lock/unlock 操作使用 acquire/release 语义就足够了,因为成功的锁定操作总是 "sees" 前一个解锁写入的值。
关于您的问题:
Is the write on x
in A
guaranteed to be observed by B
if B
locks after A
unlocks?
重要的部分是"if B
locks after A
unlocks"!如果这是有保证的,那么是的,B
的锁定操作与 A
的解锁同步,从而建立了一个 happens-before 关系。因此 B
将观察 A
的写入。但是,您的代码不提供 B
在 A
之后锁定的保证,因此您有潜在的数据竞争,这将导致@ReinstateMonica 正确指出的未定义行为。
更新
对 x 的写入在 A
的解锁之前排序。操作是否在互斥量之外(之前)并不重要。事实上,从理论上讲,编译器可以重新排序操作,以便它最终在 inside 互斥锁中结束(尽管这不太可能)。 Sequenced-before 也是 happens-before 定义的一部分,所以我们有以下内容:
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;
}};
假设 B
在 A
解锁后锁定,我们有:
- a 在 b 之前排序 -> a 在 b
- b 与 c 同步 -> b 发生在 [=51= 之前]c
并且由于先行关系是传递性的,因此 a 先行发生 c。所以是的,对于 A
解锁之前排序的 所有 操作都是如此 - 无论它们是否在锁内。
对于互斥lock()
,标准mentions:
Prior unlock() operations on the same mutex synchronize-with (as defined in std::memory_order) this operation.
这个answer试图按照标准来解释synchronize-with
是什么意思。不过,貌似定义没有明确说明。
我的主要问题是,我能得到这个输出吗:
x: 1
y: 2
由于线程 A 中的内存重新排序,以下代码?如果 A
解锁后 B
锁定,A
中的 x
上的写入是否保证被 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;
}
如果不是,基于标准的哪一部分? 换句话说,我们可以假设 lock/unlock?
之间存在顺序一致性吗?我也看到了这个
明确定义了同步关系。该标准规定如下:
Certain library calls synchronize with other library calls performed by another thread. For example, an atomic store-release synchronizes with a load-acquire that takes its value from the store. [...] [ Note: The specifications of the synchronization operations define when one reads the value written by another. For atomic objects, the definition is clear. All operations on a given mutex occur in a single total order. Each mutex acquisition "reads the value written" by the last mutex release. — end note ]
还有:
An atomic operation A that performs a release operation on an atomic object M synchronizes with an atomic operation B that performs an acquire operation on M and takes its value from any side effect in the release sequence headed by A.
所以换句话说,如果一个acquire操作A "sees"一个release操作存储的值B,那么A 与 B.
同步考虑一个自旋锁,您只需要一个原子 bool 标志。所有操作都在该标志上进行。为了获得锁,您已经使用原子读-修改-写操作设置了标志。原子对象上的所有修改完全按修改顺序排序,并且保证 RMW 操作始终读取在与该 RMW 操作关联的写入之前写入的最后一个值(按修改顺序)。
由于这种保证,lock/unlock 操作使用 acquire/release 语义就足够了,因为成功的锁定操作总是 "sees" 前一个解锁写入的值。
关于您的问题:
Is the write on
x
inA
guaranteed to be observed byB
ifB
locks afterA
unlocks?
重要的部分是"if B
locks after A
unlocks"!如果这是有保证的,那么是的,B
的锁定操作与 A
的解锁同步,从而建立了一个 happens-before 关系。因此 B
将观察 A
的写入。但是,您的代码不提供 B
在 A
之后锁定的保证,因此您有潜在的数据竞争,这将导致@ReinstateMonica 正确指出的未定义行为。
更新
对 x 的写入在 A
的解锁之前排序。操作是否在互斥量之外(之前)并不重要。事实上,从理论上讲,编译器可以重新排序操作,以便它最终在 inside 互斥锁中结束(尽管这不太可能)。 Sequenced-before 也是 happens-before 定义的一部分,所以我们有以下内容:
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;
}};
假设 B
在 A
解锁后锁定,我们有:
- a 在 b 之前排序 -> a 在 b
- b 与 c 同步 -> b 发生在 [=51= 之前]c
并且由于先行关系是传递性的,因此 a 先行发生 c。所以是的,对于 A
解锁之前排序的 所有 操作都是如此 - 无论它们是否在锁内。