C++ 标准:宽松的原子存储可以提升到互斥锁之上吗?

2024-01-30

标准中是否有任何措辞可以保证原子的宽松存储不会被提升到互斥体的锁定之上?如果不是,是否有任何措辞明确表明编译器或 CPU 这样做是合理的?

例如,采用以下程序(可能会使用 acq/rel 进行foo_has_been_set并避免锁定,和/或使foo本身是原子的。这么写就是为了说明这个问题。)

std::mutex mu;
int foo = 0;  // Guarded by mu
std::atomic<bool> foo_has_been_set{false};

void SetFoo() {
  mu.lock();
  foo = 1;
  foo_has_been_set.store(true, std::memory_order_relaxed);
  mu.unlock();
}

void CheckFoo() {
  if (foo_has_been_set.load(std::memory_order_relaxed)) {
    mu.lock();
    assert(foo == 1);
    mu.unlock();
  }
}

是否有可能CheckFoo如果另一个线程正在调用,上面的程序就会崩溃SetFoo同时,或者是否有一些保证商店foo_has_been_set无法提升至以下调用之上mu.lock由编译器和CPU决定?

这与一个较旧的问题 https://stackoverflow.com/q/24901772/1505451,但我并不是 100% 清楚那里的答案是否适用于此。特别是,该问题答案中的反例可能适用于两个并发调用SetFoo,但我对编译器知道有一个调用的情况感兴趣SetFoo并一通电话CheckFoo。这样就保证安全了吗?

我正在寻找标准中的具体引用。


我想我已经找出了保证的特定偏序边缘 程序不能崩溃。在下面的答案中我引用了版本 N4659 https://timsong-cpp.github.io/cppwp/n4659/标准草案的内容。

写入器线程A和读取器线程B涉及的代码是:

A1: mu.lock()
A2: foo = 1
A3: foo_has_been_set.store(relaxed)
A4: mu.unlock()

B1: foo_has_been_set.load(relaxed) <-- (stop if false)
B2: mu.lock()
B3: assert(foo == 1)
B4: mu.unlock()

我们寻求证明,如果 B3 执行,则 A2 在 B3 之前发生,如[种族简介]/10 https://timsong-cpp.github.io/cppwp/n4659/intro.races#10. By [种族简介]/10.2 https://timsong-cpp.github.io/cppwp/n4659/intro.races#10.2,足以证明A2线程间发生 B3之前。

因为给定互斥体上的锁定和解锁操作一次性发生 命令 ([thread.mutex.requirements.mutex]/5 https://timsong-cpp.github.io/cppwp/n4659/thread#mutex.requirements.mutex-5),我们必须有 A1 或 B2 首先来。两种情况:

  1. 假设 A1 发生在 B2 之前。然后由[线程.互斥.类]/1 https://timsong-cpp.github.io/cppwp/n4659/thread#mutex.class-1 and [线程.互斥.要求.互斥]/25 https://timsong-cpp.github.io/cppwp/n4659/thread#mutex.requirements.mutex-25,我们知道A4将与B2同步。 因此通过[种族介绍]/9.1 https://timsong-cpp.github.io/cppwp/n4659/intro.races#9.1,A4线程间发生在B2之前。由于 B2 是 在B3之前测序,通过[种族介绍]/9.3.1 https://timsong-cpp.github.io/cppwp/n4659/intro.races#9.3.1我们知道A4线程间 发生在 B3 之前。由于 A2 排序在 A4 之前,因此[种族简介]/9.3.2 https://timsong-cpp.github.io/cppwp/n4659/intro.races#9.3.2, A2 线程间发生在B3之前。

  2. 假设 B2 发生在 A1 之前。那么按照上面同样的逻辑,我们知道 B4 与 A1 同步。因此,由于 A1 排序在 A3 之前,因此[种族介绍]/9.3.1 https://timsong-cpp.github.io/cppwp/n4659/intro.races#9.3.1,B4线程间发生在A3之前。因此,由于 B1 是 在 B4 之前测序,通过[种族简介]/9.3.2 https://timsong-cpp.github.io/cppwp/n4659/intro.races#9.3.2,B1线程间发生在A3之前。 因此通过[种族简介]/10.2 https://timsong-cpp.github.io/cppwp/n4659/intro.races#10.2,B1 发生在 A3 之前。但随后根据[种族简介]/16 https://timsong-cpp.github.io/cppwp/n4659/intro.races#16,B1 必须从 A3 之前的状态获取其值。因此,加载将返回 false,并且 B2 一开始就不会运行。换句话说,这种情况不可能发生。

因此,如果 B3 执行(情况 1),则 A2 在 B3 之前发生,并且断言将通过。 ∎

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

C++ 标准:宽松的原子存储可以提升到互斥锁之上吗? 的相关文章

随机推荐