Один из способов получить std::future
через std::async
:
int foo()
{
return 42;
}
...
std::future<int> x = std::async(foo);
В этом примере, как хранилище для x
Выделено ли асинхронное состояние, и какой поток (если задействовано более одного потока) отвечает за выполнение выделения? Кроме того, клиент std::async
иметь какой-либо контроль над распределением?
Для контекста, я вижу, что один из конструкторов из std::promise
может получить распределитель, но мне неясно, можно ли настроить распределение std::future
на уровне std::async
,
Просто исходя из простых аргументов std::async
кажется, нет никакого способа контролировать распределение внутренних std::promise
и поэтому он может просто использовать что-нибудь, хотя, вероятно, std::allocator
, Хотя я предполагаю, что в теории это не определено, скорее всего, общее состояние распределено внутри вызывающего потока. Я не нашел никакой явной информации в стандарте по этому вопросу. В конце std::async
это очень специализированное средство для легкого асинхронного вызова, так что вам не нужно думать, есть ли на самом деле является 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
аргумент использовать его для выделения памяти
необходимо хранить внутренние структуры данных.
Ну, там даже не сказано является std::promise
внизу (также для std::async
), это может быть неопределенный тип, подключаемый к std::future
,
Так что, если это действительно не указано, как std::packaged_task
распределяет его внутреннее общее состояние, лучше всего было бы реализовать собственные средства для вызова асинхронных функций. Учитывая, что, просто говоря, std::packaged_task
это просто std::function
в комплекте с std::promise
а также std::async
просто начинает std::packaged_task
в новом потоке (ну, кроме случаев, когда это не так), это не должно быть слишком большой проблемой.
Но на самом деле это может быть упущение в спецификации. Принимая во внимание, что контроль распределения не совсем подходит std::async
объяснение std::packaged_task
и его использование распределителей может быть немного яснее. Но это также может быть преднамеренным, поэтому std::packaged_task
свободен использовать все, что захочет, и даже не нуждается в std::promise
внутренне.
РЕДАКТИРОВАТЬ: Читая это снова, я думаю, что приведенная выше стандартная цитата действительно говорит, что std::packaged_task
общее состояние является распределяется с использованием предоставленного распределителя, так как он является частью «внутренние структуры данных», что бы это ни было (не должно быть фактического std::promise
, хоть). Я так думаю std::packaged_task
должно быть достаточно для явного контроля общего состояния асинхронной задачи std::future
,
Память выделяется потоком, который вызывает 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
что ссылается на это, но почему бы вам?