c++ - 如何制作轻量级的加载-存储屏障

标签 c++ multithreading concurrency memory-barriers

例如,如果我们有两个 std::atomic 并且想要从第一个读取值然后标记第二个我们不再需要第一个的值。我们不希望这些操作被重新排序(否则第一个值可以在我们读取之前被重写),但是操作之间没有数据依赖性,所以我们明确需要一个屏障来防止重新排序(和 memory_order_consume 不适合)。

全栅栏在这里肯定是矫枉过正。此外,我们既不需要释放也不需要获得语义(即使它们提供了这样的障碍)。我们所需要的只是保留先读后写操作的顺序。

是否有一些廉价的栅栏可以满足我们的需求?

编辑:我需要的例子。

std::atomic<X> atomicVal;
std::atomic<bool> atomicFlag = false;
...

auto value = atomicVal.load(std::memory_order_relaxed);
some_appropriative_fence();
atomicFlag.store(true, std::memory_order_relaxed);

并且在 atomicFlag 被设置之后 atomicVal 可以被覆盖为一些进一步的值,所以我们需要在之前读取它。

当然可以

auto value = atomicVal.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
atomicFlag.store(true, std::memory_order_relaxed);

但是对于我们需要的操作来说它太昂贵了。

我很感兴趣,最小限度的栅栏足以保证操作顺序。

最佳答案

根据您的更新: https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering

你会希望原子标志和变量被写入(存储):

ptr.store(p, std::memory_order_release);

并且您希望通过以下方式读取标志和值:

p2 = ptr.load(std::memory_order_acquire)

这似乎就是它们存在的确切原因。

编辑 2:事实上,Release-Consume 可能更好。但我从未见过它被使用过。上面的链接还指出:

 Note that currently (2/2015) no known production compilers track dependency chains: consume operations are lifted to acquire operations.

编辑 3:示例代码执行与我理解的您想要的类似的操作。

#include <thread>
#include <iostream>
#include <atomic>

std::atomic<int> x;
std::atomic<int> y;

auto write_op = std::memory_order_release;
auto read_op = std::memory_order_acquire;

// auto write_op = std::memory_order_seq_cst;
// auto read_op = std::memory_order_seq_cst;

void consumer()
{
    while(true)
    {
        int rx,ry;
        do
        {
            ry = y.load(read_op); // flag read first to guarantee x validity
            rx = x.load(read_op);
        }
        while(ry == 0); // wait for y. y acts as the flag, here

        if (ry == -1)
        {
            break;
        }

        if (rx != ry) // check consistency
        {
            std::cout << "Boo " << rx << " " << ry << std::endl;
        }

        x.store(0, write_op);
        y.store(0, write_op);
    }
}

void producer()
{
    int count = 0;
    int steps = 0;
    while(steps < 50)
    {
        while(y.load(read_op) != 0) {} // wait for y to have been consumed

        int value = std::rand() % 10 + 1;

        x.store(value, write_op); // stores values
        y.store(value, write_op); // indicates readiness to other thread

        count++;
        if (count == 1000000)
        {
            std::cout << '.' << std::endl;
            count = 0;
            steps++;
        }
    }
    y.store(-1);
}

int main()
{
    x = 0;
    y = 0;

    std::thread thread1(producer);
    std::thread thread2(consumer);

    thread1.join();
    thread2.join();
}

关于c++ - 如何制作轻量级的加载-存储屏障,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52855910/

相关文章:

Java——线程执行顺序

c# - Windows 服务异常处理和计时器

c++ - 如何在 C++11 中初始化 pair<vector<pair<bool,int>>,vector<pair<bool,int>>>

c++ - 基于std::set的无序选择实现最终会重复

c++ - 如何从 C++ 数组中删除重复项?

java - 如何在多线程代码中避免ConcurrentModificationException

python - 线程在加入时卡住

c++ - 命名类(可能是代理)的目的是允许对项目进行排序和删除,而无需修改基础容器

multithreading - Cocoa:延迟一个方法调用,直到处理后续调用

java - 为什么我的多线程 Java 程序没有最大化我机器上的所有内核?