为什么习得性语义仅应用于读取而不应用于写入?

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

首先,请考虑发布语义。如果数据集受到自旋锁的保护(互斥锁等-不管使用哪种确切的实现;就目前而言,假定0表示空闲,而1-忙)。更改数据集后,线程将0存储到自旋锁地址。为了在将0存储到自旋锁地址之前强制所有先前动作的可见性,使用释放语义执行存储,这意味着在进行此存储之前,所有其他线程必须可以看到所有先前的读取和写入。这是实现细节,是通过完全存储还是单存储操作的释放标记来完成。 (我希望)毫无疑问是清楚的。

然后,转到拥有自旋锁的时刻。为了防止竞争,这是任何一种比较设置操作。通过单指令CAS实现(X86,Sparc ...),这将读写结合在一起。对于X86原子XCHG也是如此。对于LL / SC(大多数RISC),这属于:

  1. 读取(LL)自旋锁位置,直到显示自由状态。 (可以通过某种CPU停顿来优化。)
  2. 写入(SC)值“已占用”(在本例中为1)。 CPU公开操作是否成功(条件标志,输出寄存器等)
  3. 检查写入(SC)结果,如果失败,请转到步骤1。

在所有情况下,其他线程应看到的表明该自旋锁已被占用的操作,正在向其位置写入1,并且在此写入与对受自旋锁保护的数据集的后续操作之间应设置屏障。除非获得CAS或LL / SC操作许可,否则读取此自旋锁不会给保护方案带来任何好处。

但是所有真正实现的方案都允许在读取(或CAS)而不是写入时获得语义的修改。结果,LL / SC方案将需要对自旋锁进行额外的最终获取式读取操作,以提交所需的屏障。但是典型输出中没有这样的指令。例如,如果在ARM上编译:

  for(;;) {
    int e{0};
    int d{1};
    if (std::atomic_compare_exchange_weak_explicit(p, &e, d,
          std::memory_order_acquire,
          std::memory_order_relaxed)) {
      return;
    }
  }

其输出首先包含LDAXR == LL + acquire,然后是STXR == SC(其中没有障碍,因此,不能保证其他线程会看到它吗?)这可能不是我的工件,而是例如生成的。在glibc中:pthread_spin_trylock调用__atomic_compare_exchange_weak_acquire(并且没有更多的障碍),它属于GCC内置__atomic_compare_exchange_n,其中互斥量读取时获取,互斥量写入时不释放。

似乎我在此考虑中错过了一些主要细节。有人会纠正吗?

这也可能分为两个子问题:

SQ1:以类似的指令顺序:

(1) load_linked+acquire mutex_address ; found it is free
(2) store_conditional mutex_address ; succeeded
(3) read or write of mutex-protected area

什么阻止CPU重新排序(2)和(3),结果其他线程看不到互斥锁?

SQ2:是否有设计因素建议仅在负载下获得语义?

我已经看到一些示例显示了无锁代码的方向,例如:

线程1:

var = value;
flag.store(true, std::memory_order_release);

线程2:

if (flag.load(std::memory_order_acquire)) {
   // We already can access it!!!
   value = var;
   ... do something with value ...
}

但是这应该已经工作了[[之后互斥锁保护的样式可以稳定地工作。

assembly synchronization atomicity
1个回答
0
投票
其输出首先包含LDAXR == LL + acquire,然后包含STXR == SC(其中没有障碍,因此,不能保证其他线程会看到它吗?)

嗯?存储总是对其他线程可见;存储缓冲区总是尽可能快地耗尽自身。问题仅在于是否阻止以后在

this线程中进行加载/存储,直到存储缓冲区为空。 (例如,这是seq-cst pure存储所必需的。)>STXR是排他的,并且与LL绑定。

因此,它和负载在全局操作顺序中是不可分割的,

就像原子RMW操作的负载和存储端一样,就像x86在lock cmpxchg的一条指令中所做的一样。原子RMW可以提前移动(因为获得负载可以做到这一点,因此可以放松存储)。但是它以后不能移动(因为<< cant-loads>不能这样做)。 Jeff Preshing on acq and rel semantics

因此,原子RMW在关键部分中的任何操作之前都以全局顺序出现,并且足以进行锁定。

它不必等待诸如缓存未命中存储之类的早期操作;它可以让他们进入关键部分。但这不是问题。
但是,如果您had使用了acq_rel CAS,则直到完成所有较早的加载/存储后(由于存储侧的发行语义),它才能取得锁定。”>

我不确定原子RMW的acq_rel和seq_cst之间是否存在asm差异。可能在PowerPC上?不是在x86上,所有RMW都是seq_cst。不在AArch64上:它仅具有宽松和顺序发布。

LDAR + STR就像x86cmpxchg


without
一个锁前缀:获取负载并分开存储。 (由于x86内存模型的原因,x86 cmpxchg的存储方仍然是发布存储(但不是顺序发布)。

关于我的理由的其他确认,即CAS的“成功”侧的mo_acquire足以进行锁定:


[https://en.cppreference.com/w/cpp/atomic/memory_order说“互斥锁上的lock()操作也是获取操作”]

    Glibc的pthread_spin_trylock使用互斥锁上的GCC内置__atomic_compare_exchange_n,仅使用acquire_rel或seq_cst而不是acquire。我们知道许多聪明的人都在看glibc。在没有有效增强seq-cst asm的平台上,如果有的话,可能会注意到bug。
© www.soinside.com 2019 - 2024. All rights reserved.