Как выделяется хранилище, связанное с 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,

11

Решение

Просто исходя из простых аргументов 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,

6

Другие решения

Память выделяется потоком, который вызывает 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 что ссылается на это, но почему бы вам?

6