std::async
Начнем готовить наш бесчеловечный эксперимент, будто мы министерство образования. Испугались?! Не надо бояться, мы всего лишь
𝚜𝚝𝚍::𝚏𝚞𝚝𝚞𝚛𝚎<𝚒𝚗𝚝> 𝚏𝚝𝚛 = 𝚜𝚝𝚍::𝚊𝚜𝚢𝚗𝚌(𝚜𝚝𝚍::𝚕𝚊𝚞𝚗𝚌𝚑::𝚊𝚜𝚢𝚗𝚌, []{ 𝚛𝚎𝚝𝚞𝚛𝚗 𝟺𝟸; });
𝚏𝚝𝚛.𝚠𝚊𝚒𝚝();
𝚒𝚗𝚝 𝚡 = 𝚏𝚝𝚛.𝚐𝚎𝚝();
Функция std::async дает возможность запускать некоторые задачи, вроде простейшей лямбды.
Стандарт в пункте 33.10.9 Function template async [futures.async] прозрачно намекает, что первый аргумент определяет политику future.
Если передать launch::async, то функция вызывается сразу же еще и в отдельном потоке. Возвращаемое значение сохранится в некоем разделяемом объекте, т.е. внутри std::future.
А если политика launch::deferred, то это просто будет отложенный вызов функции.
Почему так происходит? Вспомним, что внутри std::future есть:
__𝚜𝚝𝚊𝚝𝚎_𝚝𝚢𝚙𝚎 _𝙼_𝚜𝚝𝚊𝚝𝚎;
Это объект типа shared_ptr<_State_baseV2>, а _State_baseV2 - это базовый класс с некоторыми основными полями.
У него есть наследники, в которых определяется поведение std::future.
Какой именно наследник будет сидеть в _M_state, определит std::async, поскольку эту функция дружит с std::future и имеет доступ к внутреннему состоянию.
Функция создаст _Async_state_impl, либо _Deferred_state. Уже по именам можно понять, кто за какую политику отвечает.
_Async_state_impl содержит в себе отдельный поток std::thread, где целевая функция начинает выполняться сразу после создания.
_Deferred_state ничего такого в составе не имеет, создан этот класс только для ленивого вычисления. Пока не запросишь wait или get, то он ничего делать не будет.
Итак, std::async возвращает объект ftr, поведение которого определено, но результирующее значение которого еще не сформировано.
Здесь мы могли бы начать иные вычисления, но мы просто подождем пока ftr не будет готово.
За это отвечает функция ftr.wait(), проинспектируем ее реализацию в std::future, чтоб не сесть в лужу при первом запуске
𝚟𝚘𝚒𝚍 𝚠𝚊𝚒𝚝() 𝚌𝚘𝚗𝚜𝚝 {
_𝚂𝚝𝚊𝚝𝚎_𝚋𝚊𝚜𝚎::_𝚂_𝚌𝚑𝚎𝚌𝚔(_𝙼_𝚜𝚝𝚊𝚝𝚎);
_𝙼_𝚜𝚝𝚊𝚝𝚎->𝚠𝚊𝚒𝚝();
}
На метод _S_check можно не обращать внимания, это просто проверка, что объект _M_state не пустой.
Строчкой ниже вызывается настоящий метод ожидания:
_𝚁𝚎𝚜𝚞𝚕𝚝_𝚋𝚊𝚜𝚎& 𝚠𝚊𝚒𝚝() {
_𝙼_𝚌𝚘𝚖𝚙𝚕𝚎𝚝𝚎_𝚊𝚜𝚢𝚗𝚌();
_𝙼_𝚜𝚝𝚊𝚝𝚞𝚜._𝙼_𝚕𝚘𝚊𝚍_𝚠𝚑𝚎𝚗_𝚎𝚚𝚞𝚊𝚕(_𝚂𝚝𝚊𝚝𝚞𝚜::__𝚛𝚎𝚊𝚍𝚢, 𝚖𝚎𝚖𝚘𝚛𝚢_𝚘𝚛𝚍𝚎𝚛_𝚊𝚌𝚚𝚞𝚒𝚛𝚎);
𝚛𝚎𝚝𝚞𝚛𝚗 *_𝙼_𝚛𝚎𝚜𝚞𝚕𝚝;
}
Метод _M_complete_async() базового класса пуст, но у потомков в нем могут размещаться какие-нибудь необходимые действия перед получением значения.
У _Deferred_state такие:
𝚟𝚒𝚛𝚝𝚞𝚊𝚕 𝚟𝚘𝚒𝚍 _𝙼_𝚌𝚘𝚖𝚙𝚕𝚎𝚝𝚎_𝚊𝚜𝚢𝚗𝚌() {
_𝙼_𝚜𝚎𝚝_𝚛𝚎𝚜𝚞𝚕𝚝(_𝚂_𝚝𝚊𝚜𝚔_𝚜𝚎𝚝𝚝𝚎𝚛(_𝙼_𝚛𝚎𝚜𝚞𝚕𝚝, _𝙼_𝚏𝚗), 𝚝𝚛𝚞𝚎);
}
В общем, это просто вычисление через call_once переданной функции и сохранение результата. Внутри, конечно, не все так просто, и есть разные интересные подробности, вроде структуры _Task_setter, но общий смысл такой.
Для _Async_state_commonV2:
𝚟𝚒𝚛𝚝𝚞𝚊𝚕 𝚟𝚘𝚒𝚍 _𝙼_𝚌𝚘𝚖𝚙𝚕𝚎𝚝𝚎_𝚊𝚜𝚢𝚗𝚌() { _𝙼_𝚓𝚘𝚒𝚗(); }
𝚟𝚘𝚒𝚍 _𝙼_𝚓𝚘𝚒𝚗() { 𝚜𝚝𝚍::𝚌𝚊𝚕𝚕_𝚘𝚗𝚌𝚎(_𝙼_𝚘𝚗𝚌𝚎, &𝚝𝚑𝚛𝚎𝚊𝚍::𝚓𝚘𝚒𝚗, &_𝙼_𝚝𝚑𝚛𝚎𝚊𝚍); }
Вот тут интереснее, мы ждем завершения потока _M_thread через call_once. Логично, ведь поток запущен для выполнения аналогичной функции _M_set_result, как и у ленивого класса.
Далее _M_load_when_equal ждет, пока состояние не перейдет в _Status::__ready. Это значит, что значение готово и передано объекту future в _M_result.
Теперь его можно и вернуть.