获得std::future
的一种方法是通过std::async
:
int foo()
{
return 42;
}
...
std::future<int> x = std::async(foo);
在这个例子中,如何分配x
的异步状态的存储,以及哪个线程(如果涉及多个线程)负责执行分配?此外,std::async
的客户是否可以控制分配?
对于上下文,我看到std::promise
的{{3}}可能会收到一个分配器,但我不清楚是否可以自定义std::future
的分配。 std::async
。
答案 0 :(得分: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
,但为什么会这样?
答案 1 :(得分:6)
仅从std::async
的仅仅参数判断,似乎没有办法控制内部std::promise
的分配,因此它可以只使用任何东西,尽管可能是std::allocator
。虽然我认为理论上它是未指定的,但很可能共享状态是在调用线程内部分配的。我没有在标准中找到关于此事的任何明确信息。最后,std::async
是一个非常专业的工具,可以轻松进行异步调用,因此您无需考虑实际上 是否std::promise
。
为了更直接地控制异步调用的行为,还有std::packaged_task
,它确实有一个allocator参数。但仅从标准引用来看,这个分配器是否仅用于为函数分配存储(因为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
),它可能只是一个可以连接到{{1}的未定义类型}}
因此,如果确实没有指定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
的共享状态。