我正在阅读n3485中定义的C++内存模型,它讨论了发布/获取语义,根据我的理解,以及本博客中给出的定义:
收购语义是可以只适用于该操作的属性读取共享内存,无论是读-修改-写操作或普通负载.然后该操作被认为是读取.获取语义可防止读取采集的内存重新排序,并按程序顺序执行任何读取或写入操作.
发布语义是一种属性,它只能应用于写入共享内存的操作,无论它们是读取 - 修改 - 写入操作还是普通存储.然后将该操作视为写入释放.释放语义通过程序顺序中的任何读取或写入操作来防止写入释放的内存重新排序.
将阻止在当前读/写完成之前或之后重新排序读/写.第一个(获取)将确保当前正在执行的读取不会在其之后的任何读/写重新排序,后者(发布)将确保当前写入未使用之前的读/写操作重新排序它.
现在可以说std::mutex::lock它将获得语义并且std::mutex::unlock本质上具有发布语义吗?
在标准中,我可以在部分下找到它
30.4.1.2互斥体类型[thread.mutex.requirements.mutex]
11同步:
unlock()对同一对象的先前操作应与(1.10)此操作同步.
从我理解的同步并没有在标准中明确定义,但它似乎是在关系看两个不同线程之间评估的两个语句之前发生的类型,但是,根据我对获取/释放语义的理解,这有更多与记忆重新排序有关. 同步也可以称为发布/获取语义?
那么发布/获取语义是否不仅适用于加载/存储操作的重新排序以及操作的线程内交错?
在关于内存模型的标准部分中,它主要讨论了两个线程交错的有序关系.这可以解释这是否也适用于内存排序.
任何人都可以澄清一下吗?
正当我以为我已经掌握了原子知识时,我看到了另一篇文章。这是GCC wiki总体摘要下的摘录:
\n -Thread 1-       -Thread 2-                   -Thread 3-\n y.store (20);    if (x.load() == 10) {        if (y.load() == 10)\n x.store (10);      assert (y.load() == 20)      assert (x.load() == 10)\n                    y.store (10)\n                  }\n\n\n释放/获取模式只需要所涉及的两个线程同步。这意味着同步值与其他线程不可交换。线程 2 中的断言仍然必须为 true,因为线程 1 和 2 与 x.load() 同步。线程 3 不参与此同步,因此当线程 2 和 3 使用 y.load() 同步时,线程 3 的断言可能会失败。线程 1 和 3 之间没有同步,因此不能为“x”假设任何值。
\n
文章说线程 2 中的断言不会失败,但线程 3 中的断言可能会失败。
\n我觉得这很令人惊讶。这是我的推理链,线程 3 断言不会失败\xe2\x80\x94也许有人可以告诉我哪里错了。
\ny …这是真的,const_cast会仅仅是一个的方式来告诉编译器"停止呻吟,把它当作一个非const指针"?有没有将const_cast本身转换为实际机器代码的情况?
考虑到创建/加入线程时隐含的同步,该代码工作所需的最小框架是什么??没有?xstd::atomicvolatile
#include <thread>
#include <cassert>
int main() {
    int x = 123; // ***
    std::thread( [ & ] { assert( x == 123 ); x = 321; } ).join();
    assert( x == 321 );
    return 0;
}
您认为在Java中实现发布/获取对的获取部分的最佳方法是什么?
我正在尝试使用经典的发布/获取语义模拟我的应用程序中的一些操作(没有StoreLoad和没有跨线程的顺序一致性).
有几种方法可以实现JDK中存储释放的粗略等效.java.util.concurrent.Atomic*.lazySet()并且sun.misc.Unsafe.putOrdered*()最常被引用的方法是做到这一点.然而,没有明显的方法来实现负载获取.
JDK API主要允许在内部lazySet()使用volatile变量,因此它们的存储版本与易失性负载配对.理论上,易失性负载应该比负载获取更昂贵,并且在前面的存储释放的上下文中不应该提供比纯粹的负载获取更多的东西.
sun.misc.Unsafe虽然这些获取方法是针对即将推出的VarHandles API计划的,但并未提供方法的getAcquire()*等效putOrdered*()方法.
听起来像它会起作用的东西是明显的负荷,接着是sun.misc.Unsafe.loadFence().有点令人不安的是,我还没有在其他任何地方看到过这种情况.这可能与它是一个非常丑陋的黑客的事实有关.
PS我很清楚JMM没有涵盖这些机制,它们不足以维持顺序一致性,并且它们创建的动作不是同步动作(例如我理解它们例如打破了IRIW).我也理解,提供的商店版本Atomic*/Unsafe通常用于急切地将引用或生产者/消费者场景中的空白作为一些重要索引的优化消息传递机制.
java concurrency multithreading memory-model memory-barriers
有问题的代码:
#include <atomic>
#include <thread>
std::atomic_bool stop(false);
void wait_on_stop() {
  while (!stop.load(std::memory_order_relaxed));
}
int main() {
  std::thread t(wait_on_stop);
  stop.store(true, std::memory_order_relaxed);
  t.join();
}
由于std::memory_order_relaxed用在这里,我假设编译器可以自由地重新排序stop.store()后t.join().结果,t.join()永远不会回来.这个推理是否正确?
如果是的话,将改变stop.store(true, std::memory_order_relaxed)以stop.store(true)解决这一问题?
这个问题是对此的跟进/澄清:
MOV x86 指令是否实现了 C++11 memory_order_release 原子存储?
这表明MOV汇编指令足以在 x86 上执行获取-释放语义。我们不需要LOCK,围栏xchg等。但是,我很难理解这是如何工作的。
英特尔文档第 3A 卷第 8 章指出:
https://software.intel.com/sites/default/files/managed/7c/f1/253668-sdm-vol-3a.pdf
在单处理器(核心)系统中......
- 读取不会与其他读取重新排序。
- 写入不会与较旧的读取重新排序。
- 对内存的写入不会与其他写入重新排序,但以下情况除外:
但这是针对单核的。多核部分似乎没有提到如何强制执行负载:
在多处理器系统中,以下排序原则适用:
- 单个处理器使用与单处理器系统相同的排序原则。
- 所有处理器都以相同的顺序观察单个处理器的写入。
- 来自单个处理器的写入与来自其他处理器的写入无关。
- 记忆排序服从因果关系(记忆排序尊重传递可见性)。
- 除了执行存储的处理器之外的处理器以一致的顺序看到任何两个存储
- 锁定指令具有总顺序。
那么如何才能MOV单独促进获取释放呢?
论文N4455 No Sane Compiler Will Optimize Atomics讨论了编译器可以应用于原子的各种优化。在有关原子的优化部分,对于 seqlock 示例,它提到了在 LLVM 中实现的转换,其中 afetch_add(0, std::memory_order_release)变成了 amfence后跟一个普通负载,而不是通常的lock addor xadd。这个想法是这样避免了对缓存行的独占访问,并且相对便宜。将mfence仍需要无论供给,以防止序限制的StoreLoad重新排序的mov生成的指令。
无论顺序如何,都会为此类操作执行此转换read-don't-modify-write,并为fetch_add(0, memory_order_relaxed).
但是,我想知道这是否合法。C++ 标准在[atomic.order]下明确指出:
原子读-修改-写操作应始终读取在与读-修改-写操作关联的写之前写入的最后一个值(按修改顺序)。
安东尼威廉姆斯之前也注意到了有关 RMW 操作看到“最新”值的事实。
我的问题是:基于原子变量的修改顺序,基于编译器是否发出lock addvsmfence后跟普通加载,线程可以看到的值的行为是否存在差异?这种转换是否可能导致执行 RMW 操作的线程加载比最新值更旧的值?这是否违反了 C++ 内存模型的保证?
对于在原子数据类型(例如std::atomic<uint8_t>)的对象上执行的存储,GCC 生成:
MOV释放存储( )情况下的指令std::memory_order_release,XCHG顺序一致存储情况下的指令( std::memory_order_seq_cst)。当目标架构为 x86_64 时。然而,当是ARM64(AArch64)时,在这两种情况下,GCC都会生成相同的指令,即STLRB. 没有生成其他指令(例如内存屏障),Clang 也会发生同样的情况。这是否意味着这条被描述为具有存储-释放语义的指令实际上也提供了顺序一致性?
例如,如果在两个内核上运行的两个线程将执行STLRB不同内存位置的存储,那么这两个存储的顺序是否唯一?这样所有其他线程都保证遵守相同的顺序吗?
我之所以问,是因为根据这个答案,使用acquire-loads ,不同的线程可能会观察到不同的release-store顺序。为了观察相同的顺序,需要顺序一致性。
现场演示: https: //godbolt.org/z/hajMKnd53
在下面的代码中
int a = A.load(std::memory_order_acquire);
T b = load_non_atomic(data);
// ---- barrier ----
int c = A.load(std::memory_order_acquire);
即使在弱内存模型架构(例如 ARM)上,load_non_atomic()我应该使用什么样的屏障来避免重新排序?c
直觉上我需要 a来禁止在它之后std::atomic_thread_fence(std::memory_order_release)重新排序读/写操作,但是它是否允许使用释放来加载?
memory-model ×10
c++ ×9
stdatomic ×3
atomic ×2
c++11 ×2
concurrency ×2
x86-64 ×2
arm64 ×1
const ×1
const-cast ×1
java ×1
llvm ×1
mutex ×1
x86 ×1