相关疑难解决方法(0)

GCC通过`memory_order_seq_cst`重新排序.这是允许的吗?

使用基本seqlock的简化版本 ,gcc load(memory_order_seq_cst)在编译代码时重新排序原子上的非原子加载-O3.在使用其他优化级别进行编译或使用clang进行编译时(甚至打开O3),不会观察到此重新排序.这种重新排序似乎违反了应该建立的同步关系,我很想知道为什么gcc重新排序这个特定的负载,如果这甚至是标准允许的话.

考虑以下load功能:

auto load()
{
    std::size_t copy;
    std::size_t seq0 = 0, seq1 = 0;
    do
    {
        seq0 = seq_.load();
        copy = value;
        seq1 = seq_.load();
    } while( seq0 & 1 || seq0 != seq1);

    std::cout << "Observed: " << seq0 << '\n';
    return copy;
}
Run Code Online (Sandbox Code Playgroud)

在seqlock程序之后,这个阅读器旋转,直到它能够加载两个实例seq_,它们被定义为a std::atomic<std::size_t>,是偶数(表示编写器当前没有写入)并且相等(表示编写器没有写入value在两个负载之间seq_).此外,因为这些负载被标记为memory_order_seq_cst(作为默认参数),我会想象指令copy = value;将在每次迭代时执行,因为它不能在初始加载时重新排序,也不能在后者下面重新排序.

但是,生成的组件会value在第一次加载之前发出负载,seq_甚至在循环之外执行.这可能导致不正确的同步或撕裂的读取value不会被seqlock算法解决.另外,我注意到这只发生在 sizeof(value) …

c++ multithreading gcc memory-barriers stdatomic

10
推荐指数
2
解决办法
174
查看次数

fetch_add(0, memory_order_relaxed/release) 到 mfence + 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++ 内存模型的保证?

c++ x86-64 llvm memory-model stdatomic

8
推荐指数
1
解决办法
272
查看次数

[C++内存模型]:如何保证读操作不会在另一次读后重新排序

在下面的代码中

int a = A.load(std::memory_order_acquire);

T b = load_non_atomic(data);

// ---- barrier ----

int c = A.load(std::memory_order_acquire);

Run Code Online (Sandbox Code Playgroud)

即使在弱内存模型架构(例如 ARM)上,load_non_atomic()我应该使用什么样的屏障来避免重新排序?c

直觉上我需要 a来禁止在它之后std::atomic_thread_fence(std::memory_order_release)重新排序读/写操作,但是它是否允许使用释放来加载?

c++ multithreading memory-model

8
推荐指数
0
解决办法
124
查看次数

无锁队列:为什么读取 `Atomic*` 两次?

我正在阅读The Art of Multiprocessor Programming, 2ed并且我注意到以下模式用于读取多个Atomic*字段:

while (true) {
    var v1 = atomic1.get();
    var v2 = atomic2.get();
    ...
    var vN = atomicN.get();
    if (v1 == atomic1.get()) {
        // do work
    }
}
Run Code Online (Sandbox Code Playgroud)

这个建设的目的是什么?


我在书中找到的唯一解释是:

... 检查读取的值是否一致 ...

我不明白这个解释。


这是LockFreeQueue,它使用了书中的这种模式:

public class LockFreeQueue<T> {
  
  AtomicReference<Node> head, tail;

  public LockFreeQueue() {
    Node node = new Node(null);
    head = new AtomicReference(node);
    tail = new AtomicReference(node);
  }

  public void enq(T value) {
    Node node = new Node(value);
    while …
Run Code Online (Sandbox Code Playgroud)

java multithreading atomic lock-free

6
推荐指数
1
解决办法
139
查看次数

用 32 位原子实现 64 位原子计数器

我想从原子 uint32s 拼凑一个 uint64 原子计数器。计数器有一个写入器和多个读取器。编写器是一个信号处理程序,所以它不能阻塞。

我的想法是使用低位的代数作为读锁。读取器重试,直到整个读取过程中生成计数稳定,并且低位未设置。

以下代码在内存排序的设计和使用中是否正确?有没有更好的办法?

using namespace std;
class counter {
    atomic<uint32_t> lo_{};
    atomic<uint32_t> hi_{};
    atomic<uint32_t> gen_{};

    uint64_t read() const {
        auto acquire = memory_order_acquire;
        uint32_t lo, hi, gen1, gen2;
        do {
            gen1 = gen_.load(acquire);
            lo = lo_.load(acquire);
            hi = hi_.load(acquire);
            gen2 = gen_.load(acquire);
        } while (gen1 != gen2 || (gen1 & 1));
        return (uint64_t(hi) << 32) | lo;
    }

    void increment() {
        auto release = memory_order_release;
        gen_.fetch_add(1, release);
        uint32_t newlo = 1 + lo_.fetch_add(1, release);
        if (newlo …
Run Code Online (Sandbox Code Playgroud)

c++ lockless c++11 stdatomic seqlock

1
推荐指数
1
解决办法
1029
查看次数