std::mutex 是否强制缓存内聚?

问题描述 投票:0回答:2

我有一个非原子变量

my_var
和一个
std::mutex my_mut
。我假设到目前为止,在代码中,程序员已经遵循了这个规则:

程序员每次修改或写入

my_var
时,都会锁定 并解锁
my_mut

假设如此,

Thread1
执行以下操作:

my_mut.lock();
my_var.modify();
my_mut.unlock();

以下是我脑海中想象的事件顺序:

  1. my_mut.lock();
    之前
    ,主内存和一些本地缓存中可能存在
    my_var
    的多个副本。即使程序员遵循了规则,这些值也不一定一致。
  2. 通过指令
    my_mut.lock();
    ,之前执行的
    my_mut
    关键部分的所有写入对于该线程在内存中都是可见的。
  3. my_var.modify();
    执行。
  4. my_mut.unlock();
    之后,主内存和一些本地缓存中可能存在
    my_var
    的多个副本。即使程序员遵循了规则,这些值也不一定一致。当下一个锁定
    my_var
    的线程锁定
    my_mut
    时,该线程末尾的
    my_mut
    的值将对它可见。

我一直很难找到一个来源来验证这正是

std::mutex
应该如何工作。我查阅了C++标准。从ISO 2013,我找到了这个部分:

[ 注意:例如,获取互斥锁的调用将执行 获取对包含互斥体的位置的操作。 相应地,释放相同互斥锁的调用将执行 在这些相同位置上发布操作。非正式地,执行 A 上的释放操作会强制对其他内存产生先前的副作用 位置对稍后执行的其他线程可见 A 上的消费或获取操作。

我对

std::mutex
的理解正确吗?

c++ caching synchronization mutex atomic
2个回答
6
投票

C++ 基于操作之间的关系进行操作,而不是某些特定的硬件术语(例如缓存内聚)。因此,C++ 标准具有“发生在之前”的关系,这大致意味着无论“发生在之前”,都完成了其所有副作用,因此在发生在之后的那一刻是可见的。 如果您输入了一个专有的关键会话,则意味着无论其中发生什么,都会在下次进入该关键部分之前发生。因此,任何相应的进入都会看到之前发生的一切。这就是标准的要求。其他一切(包括缓存内聚)都是实现的职责:它必须确保所描述的行为与实际发生的情况一致。

my_mut.unlock();

之后,主内存和一些本地缓存中可能存在my_var的多个副本。这些值不一定一致,...

0
投票
不适用于现实世界的系统。 AFAIK,没有 C++ 实现可以在没有一致缓存的情况下跨内核运行

std::thread

。存在诸如 ARM DSP + MCU 之类的异构系统,但您不会在这些内核之间运行一个程序的线程。见

什么时候在多线程中使用 易失性?

(从来没有,除了 Linux 内核代码,它确实在 GCC 和 Clang 上使用 
memory_order_relaxed

运行自己的
    volatile
  • 操作,并在需要时使用内联汇编进行更多排序。但是缓存一致的硬件这就是为什么 volatile 的作用与
    atomic
    relaxed
    的作用非常相似。)
    内存一致性需要缓存一致性吗?
    包括评论中的讨论 - 通过手动刷新实现 C++ 的一致性要求将是
    非常
    繁重,例如每个
  • release
  • 存储都必须知道要刷新缓存的哪些部分,但编译器通常不知道哪些变量是共享的或不共享的。更糟糕的是,脏回写缓存需要在其他核心写入之前被写回,以便我们以后的负载可以真正看到它们。 http://eel.is/c++draft/intro.races#19 - [注释 19: 前面的四个一致性要求实际上不允许编译器将原子操作重新排序到单个对象,即使这两个操作都放宽了负载。
    这有效地使大多数硬件提供的缓存一致性保证可用于 C++ 原子操作。
    — 尾注]
© www.soinside.com 2019 - 2024. All rights reserved.