Visual C ++CreateThreadpoolWork
在QueueUserWorkItem
使用std::async
.std::launch::async
池中的线程数是有限的。如果创建多个长时间运行而不休眠的任务(包括做 I/O),队列中即将到来的任务将没有机会工作。
标准(我使用 N4140)说使用std::async
withstd::launch::async
...调用
INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
(20.9.2, 30.3.1.2)就像在一个由线程对象表示的新执行DECAY_COPY()
线程中一样,调用async
.
(§30.6.8p3,强调我的。)
std::thread
的构造函数创建一个新线程等。
关于一般线程它说(§1.10p3):
实现应该确保所有未阻塞的线程最终都会取得进展。[注意:标准库函数可能会静默阻塞 I/O 或锁定。执行环境中的因素,包括外部强加的线程优先级,可能会阻止实现对前进进度做出某些保证。——<em>尾注]
如果我创建一堆 OS 线程或std::thread
s,它们都执行一些非常长的(可能是无限的)任务,它们都会被安排(至少在 Windows 上;不会弄乱优先级、亲缘关系等)。如果我们将相同的任务调度到 Windows 线程池(或使用std::async(std::launch::async, ...)
哪个线程池),那么在较早的任务完成之前,稍后的调度任务将不会运行。
严格来说,这合法吗?“最终”是什么意思?
问题是,如果首先安排的任务实际上是无限的,那么其余的任务将不会运行。所以其他线程(不是操作系统线程,而是根据 as-if 规则的“C++ 线程”)不会取得进展。
有人可能会争辩说,如果代码具有无限循环,则行为是未定义的,因此它是合法的。
但我认为,我们不需要标准所说的那种有问题的无限循环会导致 UB 实现这一点。访问 volatile 对象、执行原子操作和同步操作都是“禁用”关于循环终止的假设的副作用。
(我有一堆异步调用执行以下 lambda
auto lambda = [&] {
while (m.try_lock() == false) {
for (size_t i = 0; i < (2 << 24); i++) {
vi++;
}
vi = 0;
}
};
并且仅在用户输入时才释放锁定。但是还有其他有效类型的合法无限循环。)
如果我安排了几个这样的任务,我安排在他们之后的任务就不会运行。
一个非常邪恶的例子是启动太多任务,这些任务在释放锁/引发标志之前一直运行,然后使用 `std::async(std::launch::async, ...) 调度引发标志的任务. 除非“最终”这个词意味着非常令人惊讶的东西,否则这个程序必须终止。但是在 VC++ 实现下它不会!
在我看来,这似乎违反了标准。让我想知道的是笔记中的第二句话。一些因素可能会阻止实现对前进的进展做出某些保证。那么这些实现如何符合要求呢?
这就像说可能有一些因素阻止实现提供内存排序、原子性甚至多个执行线程的存在的某些方面。很好,但符合要求的托管实现必须支持多线程。对他们和他们的因素来说太糟糕了。如果他们不能提供他们那不是 C++。
这是放宽要求吗?如果这样解释,则完全撤销了该要求,因为它没有指定因素是什么,更重要的是,实现可能不提供哪些保证。
如果不是——那张纸条甚至意味着什么?
我记得根据 ISO/IEC 指令,脚注是非规范性的,但我不确定注释。我确实在 ISO/IEC 指令中找到了以下内容:
24 个音符
24.1 目的或理由
注释用于提供旨在帮助理解或使用文档文本的附加信息。该文件应可以在没有注释的情况下使用。
强调我的。如果我考虑没有那个不清楚的注释的文档,在我看来,线程必须取得进展,std::async(std::launch::async, ...)
效果好像仿函数是在一个新线程上执行的,好像它是使用创建的std::thread
,因此使用std::async(std::launch::async, ...)
must调度仿函数取得进展。在使用线程池的 VC++ 实现中,它们没有。所以VC++在这方面是违反标准的。
完整示例,在 i5-6440HQ 上的 Windows 10 Enterprise 1607 上使用 VS 2015U3 进行测试:
#include <iostream>
#include <future>
#include <atomic>
int main() {
volatile int vi{};
std::mutex m{};
m.lock();
auto lambda = [&] {
while (m.try_lock() == false) {
for (size_t i = 0; i < (2 << 10); i++) {
vi++;
}
vi = 0;
}
m.unlock();
};
std::vector<decltype(std::async(std::launch::async, lambda))> v;
int threadCount{};
std::cin >> threadCount;
for (int i = 0; i < threadCount; i++) {
v.emplace_back(std::move(std::async(std::launch::async, lambda)));
}
auto release = std::async(std::launch::async, [&] {
__asm int 3;
std::cout << "foo" << std::endl;
vi = 123;
m.unlock();
});
return 0;
}
4个或更少它终止。超过 4 则不会。
类似的问题:
是否有使用线程池的 std::async 实现?- 但它不质疑合法性,也没有答案。
std::async - 依赖于实现的用法?- 提到“线程池并不真正受支持”,但侧重于
thread_local
变量(即使答案和评论所说的“不简单”或非平凡也是可以解决的)并且没有解决接近取得进展的要求的注释。