set_value_at_thread_exit. GCC edition
Последнее время полюбились мне длительные прогулки по городу. Во-первых, это полезно для здоровья. Во-вторых, можно невозбранно любоваться красивыми видами в центре и на набережных столицы закатов. В-третьих, удобно высматривать и запоминать наиболее перспективные мусорки. Ведь ноябрь уже близко, и до конца года осталось чуть-чуть.
Думаю, все мы помним, что нам обещал один африканский юноша.
Хотя GCC тоже обещали в лучшем виде реализовать метод std::promise::set_value_at_thread_exit в 2011 году, да только полноценный метод слепили к 2015 году в GCC 5.1. Это оказалось нетривиальной задачей, ведь нужно известить всех заинтересованных в std::promise точно после вызова всех thread_local деструкторов.
Заметьте, GCC хотя бы довел дело до конца, не то что некоторые MS.
Старожилы говорят, что сразу после выхода c++11 вместо этого метода была заглушка в заголовочном файле, поэтому код компилировался, но падал замертво на этапе линковки.
В чем же сложность? Почему нельзя было просто взять и написать?
Казалось бы, этот метод похож на std::promise::set_value как и ухо на попу, только вместо _M_set_result, применяется более изощренный сценарий:
void set_value_at_thread_exit(const _Res& __r) { _M_state()._M_set_delayed_result(_State::__setter(this, __r), _M_future);
}
Оказывается, разделяемому состоянию пришлось дописывать целый метод _M_set_delayed_result, который и подготовит эффектную концовку потока и куда нужно положить результирующее значение __r, ну и само разделяемое состояние _M_future.
Да, именно. Вам не показалось.
_State& _M_state() { ... return *_M_future; }
Метод _M_state действительно возвращает объект разделяемого состояния. Однако для _M_set_delayed_result нам потребуется не просто указатель this, а умный указатель std::shared_ptr<_State> чтоб тихой сапой преобразовать его в weak_ptr!
void _M_set_delayed_result(function<_Ptr_type()> __res, weak_ptr<_State_baseV2> __self) {
bool __did_set = false;
unique_ptr<_Make_ready> __mr{new _Make_ready};
call_once(_M_once, &_State_baseV2::_M_do_set, this, std::__addressof(__res), std::__addressof(__did_set));
if (!__did_set)
__throw_future_error(int(future_errc::promise_already_satisfied));
__mr->_M_shared_state = std::move(__self);
__mr->_M_set();
__mr.release();
}
Критически важно использовать здесь именно weak_ptr: если пользователь уничтожит саму std::future и заодно еще std::promise до завершения потока, разделяемое состояние удалится. Слабый указатель не будет этому препятствовать, в отличие от полноценного shared_ptr.
Вообще, глядя на этот метод, я чешусь в самых нескромных местах. Неудивительно, что его так поздно реализовали. Разработчики долго не могли элегантно связать рантайм C++ с низкоуровневыми потоками. Потом плюнули и выложили как есть. Вот так и появился в стандартной библиотеке GCC леденящий душу класс _Make_ready.
Если в двух словах, что делается в этом методе:
Создается умный указатель unique_ptr<_Make_ready>, содержащий задачу отложенного оповещения.
Потом значение сохраняется в разделяемое состояние. Да, именно сейчас через call_once(_M_once, ...). Однако флаг готовности для future пока не выставляется, и условная переменная не оповещается.
Затем в _M_shared_state переносится слабая ссылка на разделяемое состояние.
А вот __mr->_M_set() регистрирует структуру _Make_ready для вызова метода _Make_ready::_S_run при уничтожении текущего потока.
Вызов __mr.release() освобождает указатель от ответственности. Теперь за время жизни этой структуры отвечает рантайм потока.
Что же, курок взведен. Что будет, когда грянет выстрел?