Как сделать легкий грузовой барьер - PullRequest
0 голосов
/ 17 октября 2018

Например, если у нас есть два 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 голосов
/ 17 октября 2018

После вашего обновления: 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();
}
...