在同一个互斥锁上的锁定和解锁顺序是不是一致?

Posted

技术标签:

【中文标题】在同一个互斥锁上的锁定和解锁顺序是不是一致?【英文标题】:Are lock and unlock on the same mutex sequential consistent?在同一个互斥锁上的锁定和解锁顺序是否一致? 【发布时间】:2020-06-03 02:44:53 【问题描述】:

对于互斥体lock(),标准的mentions:

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

这个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;

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

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

【问题讨论】:

您的代码具有 UB,因为从 x 读取的内容不与对 x 的写入内容独占。因此,我认为无法对内存排序进行推理。一旦允许 UB,标准就不再适用。但是一旦你解决了这个问题,那么事情肯定会被定义,1 2 输出将是不可能的。否则 1.10/10 不会成立。 如果状态 S1 和 S2 之间存在竞争,为什么我们不能推理是否处于状态 S3(即 1 2)?我认为这个问题是:***.com/questions/62164376/… 【参考方案1】:

同步关系是明确定义的。标准规定如下:

某些库调用与另一个线程执行的其他库调用同步。例如,原子存储释放与从存储中获取其值的加载获取同步。 [...] [ 注意: 同步操作的规范定义了何时读取另一个写入的值。对于原子对象,定义很明确。给定互斥体上的所有操作都以单个总顺序发生。每次互斥量获取“读取”上次互斥量释放时写入的值。 ——结束注释 ]

还有:

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

换句话说,如果获取操作A“看到”释放操作B存储的值,那么A同步-与B

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

由于这种保证,对锁定/解锁操作使用获取/释放语义就足够了,因为成功的锁定操作总是“看到”前一次解锁写入的值。

关于你的问题:

A 中的x 上的写入是否保证被B 观察到如果BA 解锁后锁定

重要的部分是“如果BA 解锁之后锁定”!如果可以保证,那么是的,B 的锁定操作与A 的解锁同步,从而建立了先发生关系。因此B 将观察A 的写入。但是,您的代码不能保证BA 之后锁定,因此您可能会发生数据竞争,这将导致@ReinstateMonica 正确指出的未定义行为。

更新 对 x 的写入是在A 解锁之前排序的。操作是否在互斥锁之外(之前)都没有关系。事实上,理论上编译器可以重新排序操作,以便它结束互斥体中(尽管这不太可能)。 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;
  ;

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

ab 之前排序 -> ab 之前发生 bc 同步 -> b 发生在 c 之前

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

【讨论】:

请注意,A 在临界区之前更改了 x。那么我们可以说如果 B 在 A 之后锁定,它会看到 A 所做的所有更改吗? A 在临界区之前和内部都进行了哪些更改? 是的。我已经更新了我的答案,以使这一点更清楚。

以上是关于在同一个互斥锁上的锁定和解锁顺序是不是一致?的主要内容,如果未能解决你的问题,请参考以下文章

为啥互斥锁没有被锁定

两个线程都在调用 boost::mutex::lock() 时阻塞

确保当前线程持有 C++11 互斥锁

使用单个命令锁定和解锁资源

提升进程间互斥锁崩溃而不是等待锁定?

perf 是不是锁定配置文件用户空间互斥锁?