Есть ли неявные барьеры памяти в C ++ - PullRequest
4 голосов
/ 01 мая 2019

В следующем коде используется атомарность, необходимая для обеспечения семантики без расы на всех платформах, или использование обещания. флаг записи стал видимым для внешнего потока?

std::atomic_bool flag{false}; // <- does this need to be atomic?
runInThreadPoolBlocking([&]() {
    // do something
    flag.store(true);
});
if (flag.load()) // do something


// simplified runInThreadPoolBlocking implementation
template <typename Callable>
void runInThreadPoolBlocking(Callable func)
{
    std::promise<void> prom;
    auto fut = prom.get_future();

    enqueueToThreadPool([&]() {
        func();
        prom.set_value();
    });

    fut.get();
}

В общем, существуют ли какие-либо "неявные" барьеры памяти, гарантируемые стандартом для таких вещей, как thread.join () или futures?

Ответы [ 2 ]

3 голосов
/ 01 мая 2019

thread.join() и promise.set_value() / future.wait() гарантируют наличие барьеров памяти.

Использование atomic_bool необходимо, если вы не хотите, чтобы компилятор переупорядочивал булеву проверку или присваивание с другим кодом. Но в этом конкретном случае вы можете использовать не атомарный bool. Это flag будет гарантированно равным true в момент проверки, если вы не используете его в каком-либо другом месте, так как назначение и проверка находятся на противоположных сторонах точки синхронизации (fut.get()) (принудительно компилятор для загрузки действительного flag значения) и функция runInThreadPoolBlocking() гарантированно завершится только после выполнения лямбды.

Цитата из cplusplus.com для future::get(), например:

Данные гонки


Будущий объект изменен. Доступ к общему состоянию осуществляется как атомарная операция (без гонок данных).

То же самое для promise::set_value(). Помимо прочего

... атомарная операция (без гонок данных) ...

означает, что ни одна из конфликтующих оценок не предшествует другой (строгий порядок в памяти).

То же самое делают все std:: многопоточные примитивы и инструменты синхронизации, где вы ожидаете, что некоторые операции будут выполняться только до или после точки синхронизации (например, std::mutex::lock() или unlock(), thread::join() и т. Д.).

Обратите внимание, что любые операции над самим объектом потока не синхронизируются с thread::join() (в отличие от операций внутри потока, который он представляет).

0 голосов
/ 01 мая 2019
std::atomic_bool flag{false}; // <- does this need to be atomic?

Да .

Звонок:

prom.get_future()

возвращает std::future<void> объект.

На будущее ссылка говорит следующее:

Шаблон класса std :: future предоставляет механизм доступа к результат асинхронных операций:

Асинхронная операция (созданная с помощью std :: async, std :: packaged_task или std :: обещание) может предоставить объект std :: future создателю этой асинхронной операции.

Создатель асинхронной операции может затем использовать различные методы для запроса, ожидания или извлечения значения из станд :: будущее. Эти методы могут блокироваться, если асинхронная операция имеет еще не предоставлено значение.

Когда асинхронная операция готова отправить результат создателю, она может сделать это путем изменения общего состояния (например, std :: prom :: set_value), который связан с std :: future создателя.

Обратите внимание, что std :: future ссылается на общее состояние, которое не используется совместно с другими асинхронными объектами возврата (в отличие от станд :: shared_future).

Вы не сохраняете здесь значение 'return', так что точка вроде как немая, и поскольку нет никаких других гарантий (и вся идея в том, что потоки могут работать параллельно в любом случае!), Вам нужно сохранить bool атомарный, если он поделился!

...