В стандарте С++ 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
, но через отдельные (связанные) объекты!
Итог: Эти объекты не должны делиться между потоками. Используйте отдельный (связанный) объект в каждом потоке.