long-运行 std::asyncs 能饿死其他 std::asyncs 吗?
Can long-running std::asyncs starve other std::asyncs?
据我了解,std::async 的通常实现会在来自预分配线程池的线程上安排这些作业。
所以假设我首先创建并安排了足够长的 运行 std::async
s 来保持该线程池中的所有线程被占用。紧接着(在他们完成执行之前不久)我还创建并安排了一些 short-运行 std::async
s。短 运行 至少有一个长 运行 完成后才执行吗?或者在标准(特别是 C++11)中是否有一些保证可以防止这种情况(比如生成更多线程以便 OS 可以在循环法中调度它们)?
标准为:
[futures.async#3.1] If launch::async is set in policy, calls INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
([func.require], [thread.thread.constr]) as if in a new thread of execution represented by a thread object with the calls to DECAY_COPY being evaluated in the thread that called async.[...]
因此,根据 as-if 规则,new 线程必须在 async()
被调用时生成 async
启动策略。当然,一个实现可能会在内部使用线程池,但是除了通常的线程创建开销之外,不会发生特殊的 'starving' 。此外,线程局部变量的初始化之类的事情应该总是发生。
事实上,clang libc++ 主干异步实现如下:
unique_ptr<__async_assoc_state<_Rp, _Fp>, __release_shared_count>
__h(new __async_assoc_state<_Rp, _Fp>(_VSTD::forward<_Fp>(__f)));
VSTD::thread(&__async_assoc_state<_Rp, _Fp>::__execute, __h.get()).detach();
return future<_Rp>(__h.get());
如您所见,内部没有使用'explicit'线程池。
此外,如您所见,here gcc 5.4.0 附带的 libstdc++ 实现仅调用普通线程。
是的,MSVC 的 std::async
似乎完全符合 属性,至少从 MSVC2015 开始是这样。
我不知道他们是否在 2017 年更新中修复了它。
这违背了标准的精神。但是,该标准对线程转发进度保证非常模糊(至少从 C++14 开始)。因此,虽然 std::async
必须表现得好像它包裹了 std::thread
,但对 std::thread
前进进度的保证非常薄弱,以至于在假设规则下这并不是一个很大的保证。
实际上,这导致我用对 std::thread
的原始调用替换线程池实现中的 std::async
,因为在 MSVC2015 中 std::thread
的原始使用似乎没有有这个问题。
我发现线程池(带有任务队列)比对 std::async
或 std::thread
的原始调用实用得多,而且编写线程池真的很容易std::thread
或 std::async
,我建议写一个 std::thread
。
您的线程池可以 return std::future
就像 std::async
一样(但没有销毁时自动阻塞功能,因为池本身管理线程生命周期)。
我读到 C++17 添加了更好的前进进度保证,但我缺乏足够的理解来断定 MSVC 的行为现在是否违反标准要求。
据我了解,std::async 的通常实现会在来自预分配线程池的线程上安排这些作业。
所以假设我首先创建并安排了足够长的 运行 std::async
s 来保持该线程池中的所有线程被占用。紧接着(在他们完成执行之前不久)我还创建并安排了一些 short-运行 std::async
s。短 运行 至少有一个长 运行 完成后才执行吗?或者在标准(特别是 C++11)中是否有一些保证可以防止这种情况(比如生成更多线程以便 OS 可以在循环法中调度它们)?
标准为:
[futures.async#3.1] If launch::async is set in policy, calls
INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
([func.require], [thread.thread.constr]) as if in a new thread of execution represented by a thread object with the calls to DECAY_COPY being evaluated in the thread that called async.[...]
因此,根据 as-if 规则,new 线程必须在 async()
被调用时生成 async
启动策略。当然,一个实现可能会在内部使用线程池,但是除了通常的线程创建开销之外,不会发生特殊的 'starving' 。此外,线程局部变量的初始化之类的事情应该总是发生。
事实上,clang libc++ 主干异步实现如下:
unique_ptr<__async_assoc_state<_Rp, _Fp>, __release_shared_count>
__h(new __async_assoc_state<_Rp, _Fp>(_VSTD::forward<_Fp>(__f)));
VSTD::thread(&__async_assoc_state<_Rp, _Fp>::__execute, __h.get()).detach();
return future<_Rp>(__h.get());
如您所见,内部没有使用'explicit'线程池。
此外,如您所见,here gcc 5.4.0 附带的 libstdc++ 实现仅调用普通线程。
是的,MSVC 的 std::async
似乎完全符合 属性,至少从 MSVC2015 开始是这样。
我不知道他们是否在 2017 年更新中修复了它。
这违背了标准的精神。但是,该标准对线程转发进度保证非常模糊(至少从 C++14 开始)。因此,虽然 std::async
必须表现得好像它包裹了 std::thread
,但对 std::thread
前进进度的保证非常薄弱,以至于在假设规则下这并不是一个很大的保证。
实际上,这导致我用对 std::thread
的原始调用替换线程池实现中的 std::async
,因为在 MSVC2015 中 std::thread
的原始使用似乎没有有这个问题。
我发现线程池(带有任务队列)比对 std::async
或 std::thread
的原始调用实用得多,而且编写线程池真的很容易std::thread
或 std::async
,我建议写一个 std::thread
。
您的线程池可以 return std::future
就像 std::async
一样(但没有销毁时自动阻塞功能,因为池本身管理线程生命周期)。
我读到 C++17 添加了更好的前进进度保证,但我缺乏足够的理解来断定 MSVC 的行为现在是否违反标准要求。