Подтвердить что ты не робот

Является ли использование `std:: get <I>` на `std:: tuple` гарантированным потокобезопасностью для разных значений` I`?

Скажем, у меня

std::tuple<T0, T1, T2> my_tuple{x0, x1, x2};

где T0, T1 и T2 являются типами значений (т.е. невозможно использовать псевдонимы).

Безопасно ли доступ к элементам my_tuple и мутировать их одновременно из нескольких потоков с помощью std::get, если каждый поток обращается к другому элементу?

Пример:

template <typename T>
void process(T& x) { /* mutate `x` */ }

// ...

std::thread{[&]{ process(std::get<0>(my_tuple)); }}.detach();
std::thread{[&]{ process(std::get<1>(my_tuple)); }}.detach();
std::thread{[&]{ process(std::get<2>(my_tuple)); }}.detach();

Инстинктивно я бы сказал, что это безопасно, поскольку my_tuple можно рассматривать как struct { T0 x0; T1 x1; T2 x2; };... но гарантируется ли это стандартом?

4b9b3361

Ответ 1

Так как std::get не имеет явных инструкций в спецификации о свойствах рассылки данных, мы возвращаемся к по умолчанию, указанному в [res.on.data.races]. В частности, параграфы 2 и 3 рассказывают историю:

Стандартная библиотека библиотеки С++ не должна прямо или косвенно обращаться к объектам (1.10), доступным для потоков, отличных от текущего потока, если прямо или косвенно обращаться к объектам через аргументы функций, включая this.

Стандартная библиотечная функция AС++ не должна прямо или косвенно изменять объекты (1.10), доступные для потоков, отличных от текущего потока, если прямо или косвенно обращаться к объектам через аргументы функции const, включая this.

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

В вашем случае несколько потоков передают один и тот же объект различным вызовам get. Поскольку вы передаете параметр не const, get будет считаться изменяющим его аргумент tuple. Поэтому вызов get на том же объекте считается модификацией объекта из нескольких потоков. И поэтому его вызов может легально спровоцировать гонку данных на tuple.

Хотя технически говоря, он просто извлекает подобъект из tuple и поэтому не должен мешать самому объекту или его другим подобъектам. Стандарт этого не знает.

Однако, если параметр был const, то get не считается спровоцировать гонку данных с другими вызовами const на get. Это просто просмотр одного и того же объекта из нескольких потоков, что разрешено в стандартной библиотеке. Это спровоцировало бы гонку данных с использованием const не const или с другими не const использованием объекта tuple. Но не с его использованием const.

Таким образом, вы можете "получить доступ" к ним, но не "изменять" их.

Ответ 2

Короткий ответ заключается в том, что он зависит от типов и того, что делает process вместо get. Сам по себе get просто возвращает адрес объекта и возвращает его в качестве ссылки. Получение адреса в основном просто считывает содержимое целых чисел. Это не повышает расовые условия. Грубо говоря, фрагмент кода в вашем вопросе является потокобезопасным тогда и только тогда, когда следующее является потокобезопасным,

T1 t1;
T2 t2;
T3 t3;

std::thread{[&]{process(t1);}}.detach();
std::thread{[&]{process(t2);}}.detach();
std::thread{[&]{process(t3);}}.detach();