Стоит ли объявлять size_t как std :: atomi c, если используется в двух потоках? - PullRequest
1 голос
/ 18 января 2020

У меня есть переменная size_t, которая обновляется на std::thread и читается другой std::thread.

Я знаю, что могу защитить мьютексом от чтения и записи. Но было бы то же самое или было бы полезно, если бы я сделал size_t как std::atomic<size_t>?

1 Ответ

3 голосов
/ 18 января 2020

Да, оно того стоит. Фактически обязательно использовать std::atomic или синхронизировать доступ к неатоми c, если несколько потоков используют одну и ту же переменную и по крайней мере один из них выполняет запись в переменную. Несоблюдение этого правила ведет к неопределенному поведению гонки данных.

В зависимости от того, как вы используете std::size_t, компилятор может предположить, что неатомные c и другие несинхронизированные переменные не будут отличаться от других потоков и оптимизировать код соответственно. Это может привести к возникновению Bad Things ™.

Мой обычный пример для этого - al oop, где используется неатоми c boolean:

// make keepRunning an std::atomic<bool> to avoid endless loop
bool keepRunning {true};
unsigned number = 0;

void stop()
{
    keepRunning = false;
}

void loop()
{
    while(keepRunning) {
        number += 1;
    }
}

При компиляции этого кода при включенных оптимизациях G CC и Clang будут проверять только keepRunning один раз и затем запускать бесконечный l oop. См. https://godbolt.org/z/GYMiLE для генерируемого вывода ассемблера.

т.е. они оптимизируют его в if (keepRunning) infinite_loop;, поднимая нагрузку из l oop. Поскольку это не атомы c, им разрешается предполагать, что никакой другой поток не может их записать. См. Программа многопоточности, застрявшая в оптимизированном режиме, но обычно работающая в -O0 для более подробного рассмотрения этой проблемы.

Обратите внимание, что этот пример показывает ошибку только, если тело l oop достаточно прост. Однако неопределенное поведение все еще присутствует, и его следует избегать с помощью std :: atomi c или синхронизации.


В этом случае вы можете использовать std::atomic<bool> с std::memory_order_relaxed, потому что вам не нужна синхронизация или упорядочение по сравнению с. другие операции в потоке записи или чтения. Это даст вам атомарность (без разрывов) и допущение, что значение может изменяться асинхронно, не заставляя компилятор использовать какие-либо инструкции asm-барьера для создания большего порядка порядка. другие операции.

Таким образом, можно и безопасно использовать атомику без какой-либо синхронизации и даже без создания синхронизации между записывающим устройством и считывателем, как это делает seq_cst или получение / освобождение загрузки и сохранения. Вы можете использовать эту синхронизацию для безопасного совместного использования переменной или массива не-Atomi c, например, с atomic<int*> buffer, который считывает читатель, когда указатель отличен от NULL.

Но если только атоми c Сама переменная является общей, вы можете просто заставить читателей читать текущее значение, не заботясь о синхронизации. Возможно, вы захотите прочитать его в локальном временном файле, если вам не нужно перечитывать каждую итерацию короткого l oop, только один раз за вызов функции.

...