В практической ситуации поток записал результат в обычное поле, затем вызвал std::promise::set_value, а другой поток получил std::future::get. Что гарантирует такая передача результата о видимости записи?
Успешное возвращение из std::future::get синхронизируется с операцией, сделавшей общее состояние future готовым, включая вызов std::promise::set_value. Поэтому записи, выполненные первым потоком до set_value, становятся видимыми второму потоку после успешного get; поле результата не обязано быть атомарным.
Это верно при условии, что между потоками нет других несинхронизированных обращений к тому же объекту и объект продолжает существовать.
Механизм promise/future появился в стандартной библиотеке C++11 как средство передачи результата между потоками с ожиданием его готовности. Он решает сразу две связанные задачи: уведомляет потребителя о завершении операции и предоставляет синхронизацию доступа к состоянию, подготовленному производителем.
До этого разработчикам приходилось вручную комбинировать флаг готовности, мьютекс, условную переменную и хранилище результата. Такая комбинация увеличивала риск пропущенных уведомлений, преждевременного чтения и ошибок в протоколе владения блокировками.
Предположим, один поток заполняет обычное поле структуры, а другой читает его после ожидания future. Само поле не является атомарным, поэтому без установленного отношения happens-before такая схема могла бы содержать гонку данных.
Ключевой риск состоит в неправильном понимании future как только механизма уведомления. Если чтение выполняется до успешного завершения ожидания общего состояния либо если после публикации снова идут несинхронизированные записи, гарантии future недостаточно.
Вызов set_value записывает значение в общее состояние promise/future и переводит его в готовое состояние. Операции, предшествующие этому вызову в потоке производителя, упорядочены перед успешным возвратом функции ожидания в потоке потребителя.
Следовательно, обычные записи, сделанные до set_value, можно безопасно читать после успешного get без атомиков и дополнительного мьютекса. Синхронизация относится не только к самому значению, переданному через future, но и к видимости предшествующих записей производителя.
В примере get возвращается только после того, как producer вызвал set_value. Запись в result.value предшествует set_value, поэтому чтение после get не образует гонку с этой записью.
Если promise устанавливает исключение через set_exception, future также становится готовым, но get выбросит исключение; рассчитывать на наличие корректно подготовленного обычного результата в этом случае нельзя. Повторно вызвать get у обычного std::future нельзя, а для нескольких потребителей нужен std::shared_future.
Future не продлевает автоматически время жизни объектов, на которые ссылается результат. Кроме того, синхронизация действует только на действия, предшествующие публикации; последующие изменения общего объекта требуют отдельной синхронизации.
Сервис запускает фоновый поток, который загружает конфигурацию в обычную структуру, после чего сообщает вызывающему потоку о завершении через promise.
Вариант с атомарным флагом готовности требует правильно подобрать порядок памяти и отдельно обеспечить безопасный доступ к структуре. Вариант с мьютексом и условной переменной универсален, но требует поддерживать предикат, блокировку и обработку ложных пробуждений.
Выбранный вариант с future удобен, когда есть одно логическое завершение операции и один потребитель результата. Он одновременно передаёт сигнал готовности и задаёт границу публикации данных. Если конфигурацию должны получать несколько потоков, результат можно передать через shared_future или применить специализированный протокол публикации.
Нет. get гарантирует видимость записей, предшествующих set_value, но не упорядочивает последующие изменения. Если producer продолжает писать в поле, а consumer одновременно читает его, возникает потенциальная гонка данных; нужны дополнительные правила владения или синхронизация.
Да, но только в пределах времени жизни объекта и при условии, что ссылка или указатель остаются валидными. Future создаёт порядок между действиями до публикации и действиями после успешного ожидания, однако не предотвращает уничтожение объекта другим потоком и не защищает от последующих несогласованных изменений.
Нет, готовность общего состояния по-прежнему служит границей публикации: каждый поток, успешно дождавшийся готовности, получает видимость предшествующих записей производителя. Но доступ к самому объекту результата должен быть безопасен для одновременного использования; shared_future синхронизирует получение результата, а не произвольные последующие мутации разделяемого объекта.