Async, futures и promises
Как получить результат и исключение из параллельной работы
std::thread умеет выполнять работу, но ничего не возвращает, а любое выброшенное им исключение аварийно завершает программу. Библиотека futures добавляет односторонний канал - разделяемое состояние (shared state): производитель в какой-то момент записывает в него значение (или исключение), а потребитель читает это значение через std::future, блокируясь только тогда, когда результат действительно нужен. Так запуск работы отделяется от получения её результата.
- запускать задачу через
std::asyncи читать её результат черезstd::future, - осознанно выбирать между
launch::asyncиlaunch::deferred, - вручную соединять производителя и потребителя через
std::promise, включая исключения, - оборачивать любой вызываемый объект в
std::packaged_taskдля пула потоков, - избегать классических ловушек: блокирующий
~future, одноразовыйget()и тихую отложенность.
1. Почему обычных потоков недостаточно
Вы уже знакомы с std::thread из основ многопоточности. Для самого потока вызываемый объект возвращает void: встроенного способа вернуть значение нет, а если задача бросает исключение, оно не может пересечь границу потока, и среда выполнения вызывает std::terminate. Чтобы вернуть результат вручную, понадобились бы общая переменная плюс std::mutex и std::condition_variable для сигнала "готово" - много хрупкой обвязки ради обычного "выполни это и дай мне ответ".
Заголовок <future> прячет ровно эту обвязку за чистым интерфейсом. Три взаимодействующие части лежат поверх одного скрытого разделяемого состояния:
std::future<T>- читающий конец. Кто-то со временем положит сюдаT(или исключение);get()извлекает его, блокируясь до готовности.std::promise<T>- ручной пишущий конец, который вы заполняете сами черезset_valueилиset_exception.std::async/std::packaged_task- удобные производители: они создают пишущий конец из вызываемого объекта и автоматически заполняют его возвращаемым значением.
shared_ptr.