如何制作轻量级的储物障碍

时间:2018-10-17 13:19:43

标签: c++ multithreading concurrency memory-barriers

例如,如果我们有两个std::atomic,并且想先读取值,然后再标记第二个,则我们不再需要first的值。我们不希望对这些操作进行重新排序(否则在读取前可以重写第一个值),但是操作之间没有数据依赖性,因此我们明确需要一个障碍来防止重新排序(并且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);

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

我很有趣的是,最小的围栏足以保证操作顺序。

1 个答案:

答案 0 :(得分:1)

更新后: 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:实际上,发布消费可能会更好。但是我从来没有看到它使用过。上面的链接还指出:

 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();
}