Могут ли несколько потоков безопасно записать одно и то же значение в одну и ту же переменную одновременно? - PullRequest
2 голосов
/ 09 января 2020

Может ли несколько потоков безопасно записать одно и то же значение в одну и ту же переменную в одно и то же время?

Для конкретного примера c - гарантируется ли приведенный ниже код стандартом C ++ для компиляции, запуска без неопределенного поведения и выведите «true» на каждой соответствующей системе?

#include <cstdio>
#include <thread>

int main()
{
    bool x = false;
    std::thread one{[&]{ x = true; }};
    std::thread two{[&]{ x = true; }};
    one.join();
    two.join();
    std::printf(x ? "true" : "false");
}

Это теоретический вопрос; Я хочу знать, работает ли он всегда, а не работает ли он на практике (или хорошо ли писать такой код :)). Я был бы признателен, если бы кто-то мог указать на соответствующую часть стандарта. По моему опыту, это всегда работает на практике, но, не зная, гарантированно ли это работает, я всегда использую std::atomic - я хотел бы знать, строго ли это необходимо для этого конкретного случая c.

Ответы [ 2 ]

6 голосов
/ 09 января 2020

Нет.

Вам необходимо синхронизировать доступ к этим переменным, используя мьютексы или делая их атомами c.

Нет исключения для того, когда записывается то же самое значение. Вы не знаете, какие шаги предпринимаются при написании этого значения (что является основной практической задачей), а также не соответствует стандарту, из-за которого код имеет неопределенное поведение ... что означает, что ваш компилятор может просто создать беспорядок в вашей программе (и это реальная проблема, которую вам следует избегать).

Кто-то придет и скажет вам, что такая-то архитектура гарантирует, что atomi c пишет в переменные такого размера. Но это не меняет аспект UB.

Вы ищете следующие отрывки:

[intro.races/2]: две оценки выражений конфликт , если один из них изменяет ячейку памяти ([intro.memory]), а другой читает или изменяет ту же ячейку памяти.

[intro.races/21]: […] Выполнение программы содержит гонку данных , если она содержит два потенциально одновременных конфликтующих действия, […]. Любая такая гонка данных приводит к неопределенному поведению.

… и окружающей его формулировке. Этот раздел на самом деле довольно эзотерический c, но вам не нужно его анализировать, так как это классическая c гонка данных из учебников, о которой вы можете прочитать в любой книге по программированию.

2 голосов
/ 09 января 2020

Легкость правильная и точная с точки зрения стандартов.

Но я дам вам еще одну точку зрения, почему это не очень хорошая идея с точки зрения аппаратной архитектуры.

Без барьер памяти (atomi c, mutex и т. д. c ...), вы можете столкнуться с так называемой проблемой когерентности кэша. На многоядерном или многопроцессорном компьютере оба потока могут установить x на true, но ваш основной поток потенциально может вывести false, даже если ваш компилятор не переведет sh x в регистр. Это связано с тем, что аппаратный кэш, используемый основным потоком, не был обновлен, чтобы x стал недействительным из какой-либо строки кеша, в которой он включен. Типы atomi c и защитные блокировки, предоставляемые C ++ (вместе с бесчисленными примитивами ОС), реализованы для решения этой проблемы.

В любом случае, Google для Проблема когерентности кэша и Cache Coherence Multicore . А для конкретной реализации архитектуры реализации транзакций atomi c найдите префикс Intel LOCK .

...