Ошибка считывания/записи переменной в 3х потоках с использованием atomic и без (с++)

103
09 сентября 2019, 16:30

У нас есть 2 потока, которые асинхронно записывают в переменную tmp числа 322 и 1337. Странность в том, что даже если переменная atomic, условие все-таки иногда срабатывает (причем даже намного чаще, чем если бы переменная было просто int), но в cout все-таки вводится либо 1337 или 322. Объясните пожалуйста как все-таки защитить переменную от считывания во время записи. Казалось бы что на 32битных системах все переменные до 4 байт должны записываться атомарно. Вот код:

#include "pch.h"
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
int main()
{
    setlocale(LC_ALL,"rus");
    atomic<int> tmp = 322;
    thread th0([&tmp]() {
        for (int i = 0; i < 100000000; i++) { tmp = 322; this_thread::sleep_for(chrono::microseconds(2));
        }
    });
    thread th1([&tmp]() {
        for (int i = 0; i < 100000000; i++) { tmp = 1337; this_thread::sleep_for(chrono::microseconds(3));
        }
    });
    for (int i = 0; i < 10000000; i++) {
        if(tmp!=322&&tmp!=1337)cout << "Ошибка =" <<tmp<< endl;
        this_thread::sleep_for(chrono::microseconds(1));
    }
    th0.detach();
    th1.detach();
}
Answer 1

Все тут атомарно (естественно, кроме вычисления всего выражения в if).

А чего вы ожидали?

Объявив tmp atomic вы заставили компилятор обращаться к памяти при каждом упоминании tmp.

Соответственно, в if(tmp!=322&&tmp!=1337) значение tmp будет выбираться дважды.
Если первое чтение вернуло 1337, а второе 322, то выполнится

cout << "Ошибка =" <<tmp<< endl;,

что вы иногда (думаю, довольно редко) и наблюдаете.

Answer 2

Операции tmp!=322 и tmp!=1337 не атомарны, так как std::atomic не имеет функций operator!=, и даже если бы имел то операция && между ними точно не атомарна

Answer 3

Проблема в том, что пока происходит проверка на условие, переменная иногда успевает менять свое значение. Для того чтобы избежать такой проблемы, нужно сохранить во временную переменную значение переменной перед началом проверки

for (int i = 0; i < 10000000; i++) {
    int ntmp = tmp;
    if(ntmp !=322&& ntmp !=1337)cout << "Ошибка =" <<ntmp<< endl;
    this_thread::sleep_for(chrono::milliseconds(1));
}
READ ALSO
Необычное разбиение строки

Необычное разбиение строки

Имеется программа для разбиения строки по регулярному выражению (парсинг)Программа читает строку из файла, разбивает по ";", вписывает в вектор...

98
Создание метода класса

Создание метода класса

Имеется класс, полем которого является структура, а один из методов это работа с этой структуройПри инициализации метода ругается на неправильное...

125
Проблемы с коррекцией движения объекта

Проблемы с коррекцией движения объекта

Хочу сделать что-то типа джойстика на андроидеЕсть квадратный тип и круглый тип

96
JavaFX 11 + Eclipse

JavaFX 11 + Eclipse

Ранее я использовал Java 8 , где была включена JavaFXХочу настроить работу JavaFX 11 используя Eclipse

136