C++, инициализация атомарных объектов

197
28 декабря 2021, 09:10

У меня есть вопрос, касающийся инициализации атомарных объектов.

Николай Джосаттис пишет:

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

То есть, если конкретнее, то мой вопрос заключается в следующем:

class Object
{
    public:
    Object()
    :
    flag{ true }// 1
    {
        flag.store(true);// 2
        std::atomic_init(&flag, true);// 3
    }
    private:
    std::atomic<bool> flag;
};

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

Есть ли какие-то отличия между этими вариантами?

Answer 1

Достаточно инициализировать flag первым способом:

class Object
{
    public:
    Object()
    :
    flag{ true }// 1
    {}
    private:
    std::atomic<bool> flag;
};

В первом случае flag будет инициализирован значением true ещё до начала исполнения функции конструктора.

Во втором случае на момент начала исполнения функции конструктора flag не будет инициализирован. Уже в самой функции вызывается метод store, который сохраняет переданное значение в flag (что эквивалентно flag = true;).

Правка:

std::atomic_init(&flag, true);// 3

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

Кроме этого, поведение будет неопределённым, если объект не был инициализирован по умолчанию, или если эта функция будет вызвана дважды для одного и того же объекта.

Первый способ даёт возможность безопасно инициализировать flag, без опасений вызвать неопределённое поведение, поэтому, на мой взгляд, atomic_init не стоит использовать в вашем случае.

Правка №2:

Изучая документацию для std::atomic<T>, я обнаружил что процесс инициализации атомарного типа std::atomic<T> с помощью конструктора

constexpr atomic( T desired ) noexcept;

не является атомарной операцией:

2) Initializes the underlying value with desired. The initialization is not atomic.

В черновике стандарта C++ по этому поводу приведено замечание ([atomics.types.operations]):

[Note: It is possible to have an access to an atomic object A race with its construction, for example by communicating the address of the just-constructed object A to another thread via memory_-order::relaxedoperations on a suitable atomic pointer variable, and then immediately accessing A in the receiving thread. This results in undefined behavior.— end note]

смысл которого в том, что присутствует возможность для возникновения состояния гонки между конструированием атомарного объекта и обращением к нему в том случае, когда указатель на только что созданный атомарный объект передаётся другому потоку исполнения, который сразу же обращается к этому объекту.

Поэтому, то, что я написал ранее

Первый способ даёт возможность безопасно инициализировать flag

не совсем верно.

Корректно было бы сказать, что с помощью первого способа гораздо сложнее наступить на грабли, по сравнению с третьим способом, приведённым вами (использование std::atomic_init).

Если вы не передаёте указатель на flag в другой поток исполнения до того как объект Object создан, то использование первого способа является безопасным, и с атомарным объектом всё будет в порядке.

READ ALSO
C++, деструктор std::future

C++, деструктор std::future

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

168
C++, std::async() и не статические методы

C++, std::async() и не статические методы

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

229
При повторном переборе массива всегда возвращается последнее значение

При повторном переборе массива всегда возвращается последнее значение

Вроде простейшая задача, но какая-то фигняСам код:

225
ACMP Сортировка выбором

ACMP Сортировка выбором

В этой задаче вам предлагается реализовать сортировку выбором

126