set_value_at_thread_exit. thread lightly


Сегодня проснулся затемно, глянул в окно, а улицу вовсю яростно хлещет снег. Тут я и осознал, что лето точно закончилось. Лето - словно поток, который мы запускаем весной и ждем в std::future от него приятных моментов. В этом потоке происходят все эти моменты, и их затягивает в std::promise через set_value_at_thread_exit. Поэтому мы понимаем, насколько было хорошо, только когда лето закончилось. Каждый год удивляюсь, как же так выходит.
А лето просто создает специальную записочку _Make_ready, которая не даст мирозданию забыть оповестить нас о том, что все приятные теплые вечера и красивые закаты на Волге уже прошли. Записка эта имеет простую структуру:

struct _Make_ready final : __at_thread_exit_elt {
weak_ptr<_State_baseV2> _M_shared_state;
static void _S_run(void*);
void _M_set();
};

Видно, что наследуется она от еще более простой:

struct __at_thread_exit_elt {
__at_thread_exit_elt* _M_next;
void (*_M_cb)(void*);
};

Ну, мы все понимаем, для чего подобное используют. Член _M_next тут явно служит для организации связанного однонаправленного списка. Это просто обычный советский список функций обратного вызова - колбэков, короче. Очевидно, при выходе поток просто вызовет всю связанную с ним цепочку из __at_thread_exit_elt.
В составе _Make_ready есть указатель на разделяемое состояние и две интересных функции. Метод _M_set, как мы помним, вызывается для регистрации в этом списке. Если порыться в исходниках GCC, можно найти нечто такое:

void __future_base::_State_baseV2::_Make_ready::_M_set() {
_M_cb = &_Make_ready::_S_run;
__at_thread_exit(this);
}

В качестве колбэка мы указываем _Make_ready::_S_run - собственно, для этого метод и объявлен статическим. Далее объект _Make_ready должен зарегистрироваться в связанном списке через некую функцию __at_thread_exit. Это почти та же известная нам __cxa_thread_atexit, только механизм более высокого уровня абстракции C++, подходящий для библиотек, которые обычно слышать ничего не хотят ни про какие __dso_handle.
Можно было бы погружаться и дальше, только в нашем колхозе методы _M_set и _S_run не определены. Иными словами, нужно написать свою функцию, которая бы формировала связанный список и цепляла его исполнение куда-нибудь в функцию запуска потока execute_native_thread_routine перед vTaskDelete. Ну, или честно сделать через TLS, если вы перверсивный перфекционист. Я же склоняюсь к варианту от разработчиков MS: ничего не реализовывать.
Зато в _S_run я успел прописать вот это:

void __future_base::_State_baseV2::_Make_ready::_S_run(void* p) {
unique_ptr<_Make_ready> mr{static_cast<_Make_ready*>(p)};
if (auto state = mr->_M_shared_state.lock()) {
state->_M_set_status();
}
}

Поскольку в предполагаемом __at_thread_exit мы регистрировались как this, то, пользуясь лучшими некромантскими практиками, восстанавливаем умный указатель для _Make_ready. Мы когда-то уже создавали unique_ptr с {new _Make_ready} и так его и не освободили, так что тут всё законно. Теперь объект точно не уйдет - при выходе из функции он будет гарантированно уничтожен.
Наконец, мы захватываем разделяемое состояние и в случае успеха выставляем статус готовности. Теперь, наконец-то, все зависшие на future.wait() потоки вздохнут свободно. Наконец-то они узнают, что результат готов.
Просмотры: 21
Открыть в MAX Сообщить о неточности

Репосты публикации

Публикация канала

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти