Как распределяется хранилище, связанное с std:: future?

Один из способов получить std::future - через std::async:

int foo()
{
  return 42;
}

...

std::future<int> x = std::async(foo);

В этом примере, как распределяется хранилище для x асинхронного состояния, и какой поток (если задействовано более одного потока) отвечает за выполнение распределения? Кроме того, имеет ли клиент std::async какой-либо контроль над распределением?

В контексте я вижу, что один из конструкторов std::promise может получить распределитель, но мне это не понятно, если оно можно настроить распределение std::future на уровне std::async.

Ответ 1

Просто судя по простым аргументам std::async, похоже, нет способа контролировать выделение внутреннего std::promise, и это поэтому можно просто использовать что-либо, хотя вероятно, std::allocator. Хотя я предполагаю, что в теории это не указано, вероятно, разделяемое состояние выделено внутри вызывающего потока. Я не нашел никакой явной информации в стандарте по этому вопросу. В конце std::async - очень специализированное средство для легкого асинхронного вызова, поэтому вам не нужно думать, действительно ли a std::promise.

Для более прямого контроля за поведением асинхронного вызова также существует std::packaged_task, который действительно имеет аргумент распределителя. Но из простой стандартной цитаты не совсем понятно, используется ли этот распределитель для распределения памяти для функции (поскольку std::packaged_task является видом специального std::function) или если он также используется для распределения общего состояния внутренний std::promise, хотя кажется вероятным:

30.6.9.1 [futures.task.members]:

Эффекты: создает новый объект packaged_task с общим состоянием и инициализирует хранимую задачу объектов с помощью std::forward<F>(f). конструкторы, которые принимают аргумент Allocator, используют его для выделения памяти необходимых для хранения внутренних структур данных.

Ну, он даже не говорит о том, что a std::promise внизу (аналогично для std::async), это может быть просто тип undefined, подключаемый к std::future.

Итак, если действительно не указано, как std::packaged_task выделяет свое внутреннее разделяемое состояние, лучшим вариантом может быть реализация ваших собственных возможностей для вызова асинхронных функций. Учитывая, что, просто говоря, a std::packaged_task - это просто std::function, связанный с std::promise и std::async, просто запускает std::packaged_task в новом потоке (ну, если только это не так), это shouldn ' t быть слишком большой проблемой.

Но на самом деле это может быть недосмотр в спецификации. В то время как управление распределением не подходит для std::async, объяснение std::packaged_task и его использование распределителей может быть немного яснее. Но это также может быть преднамеренным, поэтому std::packaged_task может свободно использовать все, что захочет, и даже не нуждается в std::promise внутри.

EDIT: Снова прочитав его, я думаю, что приведенная выше стандартная цитата действительно говорит, что std::packaged_task разделяемое состояние выделено с помощью предоставленного распределителя, поскольку оно является частью из "внутренних структур данных", независимо от того, что (там не обязательно должно быть фактическое std::promise). Поэтому я думаю, что std::packaged_task должно быть достаточно для явного управления общим состоянием асинхронной задачи std::future.

Ответ 2

Память выделяется потоком, который вызывает std::async, и вы не можете контролировать, как это делается. Как правило, это будет сделано с помощью некоторого варианта new __internal_state_type, но нет гарантии; он может использовать malloc или распределитель, специально выбранный для этой цели.

От 30.6.8p3 [futures.async]:

"Эффекты: первая функция ведет себя так же, как вызов второй функции с аргументом политики launch::async | launch::deferred и теми же аргументами для F и Args. Вторая функция создает совместно используемое состояние, которое связано с возвращенным будущим объектом..."

"Первая функция" - это перегрузка без политики запуска, а вторая - перегрузка с политикой запуска.

В случае std::launch::deferred нет другого потока, поэтому все должно произойти в вызывающем потоке. В случае std::launch::async 30.6.8p3 продолжает:

- если policy & launch::async отличен от нуля - вызывает INVOKE (DECAY_COPY (std::forward<F>(f)), DECAY_COPY (std::forward<Args>(args))...) (20.8.2, 30.3.1.2), как если бы в новом потоке выполнения, представленном объектом потока , с вызовами DECAY_COPY () оценивается в потоке, который называется async....

Я добавил акцент. Поскольку копия функции и аргументов должна выполняться в вызывающем потоке, это по существу требует, чтобы разделяемое состояние выделялось вызывающим потоком.

Конечно, вы могли бы написать реализацию, которая запустила новый поток, дождалась, когда он выделит это состояние, а затем вернул future, который ссылался на это, но зачем вам?