Есть ли безопасный способ вызвать wait() на std:: future?
В стандарте С++ 11 говорится:
30.6.6 Следующий шаблонный шаблон
(3) "Эффект вызова любой функции-члена, отличной от деструктора, оператор присваивания перемещения или действителен для будущего объекта, для которого valid() == false
- undefined."
Итак, означает ли это, что следующий код может встретить поведение undefined?
void wait_for_future(std::future<void> & f)
{
if (f.valid()) {
// what if another thread meanwhile calls get() on f (which invalidates f)?
f.wait();
}
else {
return;
}
}
Q1: Это действительно возможное поведение undefined?
Q2: Есть ли стандартный способ, позволяющий избежать возможного поведения undefined?
Обратите внимание, что стандарт имеет интересную заметку [также в 30.6.6 (3)]:
"[Примечание. Реализации поощряются чтобы обнаружить этот случай и бросить объект типа future_error с помощью условие ошибки future_errc::no_state
. -endnote]"
Q3: Это нормально, если я просто полагаюсь на стандартную ноту и просто использую f.wait()
без проверки f
действительности?
void wait_for_future(std::future<void> & f)
{
try {
f.wait();
}
catch (std::future_error const & err) {
return;
}
}
EDIT: Резюме после получения ответов и дальнейших исследований по теме
Как оказалось, реальная проблема с моим примером была не напрямую из-за параллельных модификаций (из одного потока вызывается один модифицирующий get
, другой поток, называемый valid
и wait
, который должен быть безопасным).
Реальная проблема заключалась в том, что функция std::future
object get
была доступна из другого потока, который не предназначен для использования! Объект std::future
должен использоваться только из одного потока!
Единственный другой поток, который задействован, - это поток, который устанавливает общее состояние: через возврат из функции, переданной в std::async
, или вызов set_value
для связанного объекта std::promise
и т.д.
Больше: даже wait
Включение объекта std::future
из другого потока не является предполагаемым поведением (из-за того же самого UB, что и в моем примере # 1). Мы будем использовать std::shared_future
для этого случая использования, каждый поток которого имеет собственную копию объекта std::shared_future
. Обратите внимание, что все это не через один и тот же общий объект std::future
, но через отдельные (связанные) объекты!
Итог:
Эти объекты не должны делиться между потоками. Используйте отдельный (связанный) объект в каждом потоке.
Ответы
Ответ 1
Обычный std::future
сам по себе не является потокобезопасным. Так что да, это UB, если вы вызываете модификационные функции из нескольких потоков на одном std::future
, поскольку у вас есть потенциальное состояние гонки. Хотя вызов wait
из нескольких потоков в порядке, поскольку он const/non-modify.
Однако, если вам действительно нужно получить доступ к возвращаемому значению std::future
из нескольких потоков, вы можете сначала вызвать std::future::share
в будущем, чтобы получить std::shared_future
, который вы можете скопировать в каждый поток, а затем каждый поток может вызвать get
. Обратите внимание, что важно, чтобы каждый поток имел свой собственный объект std::shared_future
.
Вам нужно только проверить правильность, если возможно, что ваше будущее может быть недействительным, что не относится к нормальным нормам usecases (std::async
и т.д.) и правильному использованию (например: не callig get
дважды).
Ответ 2
Фьючерсы позволяют сохранять состояние из одного потока и извлекать его из другого. Они не обеспечивают дополнительную безопасность потока.
Это действительно возможное поведение undefined?
Если у вас есть два потока, пытающихся получить состояние будущего без синхронизации, да. Я понятия не имею, почему вы можете это сделать.
Есть ли стандартный способ, позволяющий избежать возможного поведения undefined?
Попробуйте только получить состояние из одного потока; или, если вам действительно нужно поделиться им между потоками, используйте мьютекс или другую синхронизацию.
Это нормально, если я просто полагаюсь на стандартную заметку
Если вы знаете, что единственные реализации, которые вам нужны для поддержки, следуют этой рекомендации, да. Но не должно быть необходимости.
и просто используйте f.wait()
без проверки f
достоверности?
Если вы не делаете каких-либо странных махинаций с несколькими потоками, обращающимися к будущему, вы можете просто предположить, что они действительны до тех пор, пока вы не восстановите состояние (или не переместили его в другое будущее).