问题 与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


11589
2017-10-11 07:24


起源



答案:


仅仅从仅仅的论点来判断 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 分配其内部共享状态,最好的办法可能是为异步函数调用实现自己的工具。考虑到这一点,简单地说,a 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
2017-10-11 08:04





内存由调用的线程分配 std::async,你无法控制它是如何完成的。通常它将由一些变体完成 new __internal_state_type,但没有保证;它可能会使用 malloc,或专门为此目的选择的分配器。

从30.6.8p3 [futures.async]:

“效果:第一个函数的行为与使用策略参数调用第二个函数的行为相同 launch::async | launch::deferred 和相同的论点 F 和 Args。第二个函数创建与返回的future对象关联的共享状态。 ......”

“第一个功能”是没有启动策略的过载,而第二个功能是启动策略的重载。

如果是 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
2017-10-11 08:01



在哪里可以保证调用线程分配存储?好吧,这很可能,但我仍然希望看到标准的引用。 - Christian Rau
更新了答案以及更多细节。 - Anthony Williams
事实上,似乎很清楚,我忽略了这一点。但是像你现在这样在你的答案中指出这一点不会造成任何伤害。谢谢,+ 1。 - Christian Rau
@AnthonyWilliams用户可能关心分配存储的线程的一个原因是局部性。如果创建的线程由 std::async 在核心上执行,可以更快地访问特定的内存区域,然后在该区域中定位异步状态是有意义的。 - Jared Hoberock


答案:


仅仅从仅仅的论点来判断 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 分配其内部共享状态,最好的办法可能是为异步函数调用实现自己的工具。考虑到这一点,简单地说,a 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
2017-10-11 08:04





内存由调用的线程分配 std::async,你无法控制它是如何完成的。通常它将由一些变体完成 new __internal_state_type,但没有保证;它可能会使用 malloc,或专门为此目的选择的分配器。

从30.6.8p3 [futures.async]:

“效果:第一个函数的行为与使用策略参数调用第二个函数的行为相同 launch::async | launch::deferred 和相同的论点 F 和 Args。第二个函数创建与返回的future对象关联的共享状态。 ......”

“第一个功能”是没有启动策略的过载,而第二个功能是启动策略的重载。

如果是 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
2017-10-11 08:01



在哪里可以保证调用线程分配存储?好吧,这很可能,但我仍然希望看到标准的引用。 - Christian Rau
更新了答案以及更多细节。 - Anthony Williams
事实上,似乎很清楚,我忽略了这一点。但是像你现在这样在你的答案中指出这一点不会造成任何伤害。谢谢,+ 1。 - Christian Rau
@AnthonyWilliams用户可能关心分配存储的线程的一个原因是局部性。如果创建的线程由 std::async 在核心上执行,可以更快地访问特定的内存区域,然后在该区域中定位异步状态是有意义的。 - Jared Hoberock