Является ли атомарный объект небезопасным в каком-либо контексте, где простой объект безопасен? - PullRequest
2 голосов
/ 02 июня 2019

У меня простой вопрос: существует ли какой-либо параллельный алгоритм, в котором использование объекта тривиального типа T является законным и безопасным (четко определенным, гарантированно работающим), а использование объекта std::atomic<T> ведет к неопределенному или неопределенному поведению ? Другими словами, может ли неатомарный тип обеспечить более сильные гарантии, чем атомарный тип?

Являются ли все гарантии видимости памяти действительными для атомарных объектов, когда они действительны для обычных объектов?

[Это будет означать, что компилятор может обрабатывать (ослабленные) атомарные и неатомарные операции одинаково, упрощая промежуточное представление.]

Если да, это формально доказуемо?

Конечно, типы T такие, что std::atomic<T> не является действительной специализацией std::atomic, не считаются.

1 Ответ

1 голос
/ 02 июня 2019

Нет сценария, в котором неатомарное значение имеет более надежную гарантию по сравнению с эквивалентом atomic.

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

Рассмотрим следующую гипотетику:

class Volume
{
    atomic<int> _width;
    atomic<int> _height;
    atomic<int> _depth;
public:
   int computeValue() {return (_width*_height*_depth);}
   void SetInternals(int w, int h, int d) {_height=h;_width=w;_depth=d;}
};

Где поток 1 делает это:

f->SetInternals(42, 100, 200);

И поток 2 делает это одновременно.

int something_important = f->computeValue();

Вы видите, что значение, возвращаемое computeValue, может быть повреждено, если оба потока выполняются одновременно.

Вы можете достичь желаемого поведения, безопасного для потоков, используя мьютекс и блокировку вместо атомики:

class Volume
{
    int _width;
    int _height;
    int _depth;
    mutex _mutex;
public:
   int computeValue() {
        lock_guard<mutex> lck(_mutex);
        int result = _width*_height*_depth;
        return result;
   }
   void SetInternals(int w, int h, int d) {
      lock_guard<mutex> lck(_mutex);
      _height=h;_width=w;_depth=d;
   }
};
...