con*_*nio 27 c++ visual-c++ language-lawyer stdasync
VISUAL C++使用Windows线程池(Vista的CreateThreadpoolWork
,如果可用,QueueUserWorkItem
如果不)调用时std::async
用std::launch::async
.
池中的线程数是有限的.如果创建多个运行很长时间而没有休眠的任务(包括执行I/O),则队列中即将发生的任务将无法工作.
标准(我使用N4140)表示,借助std::async
与std::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或锁定.执行环境中的因素(包括外部强加的线程优先级)可能会阻止实现对前进进度做出某些保证.- 结束说明 ]
如果我创建了一堆OS线程或std::thread
s,都执行一些非常长(或许是无限)的任务,它们都将被安排(至少在Windows上;不会弄乱优先级,亲和力等).如果我们将相同的任务安排到Windows线程池(或使用std::async(std::launch::async, ...)
哪个任务),则在先前的任务完成之前,后续计划的任务将不会运行.
严格来说,这是合法的吗?什么"最终"意味着什么?
问题是如果首先安排的任务事实上是无限的,那么剩下的任务就不会运行.所以其他线程(不是OS线程,但根据as-if规则的"C++ - 线程")将无法取得进展.
有人可能会争辩说,如果代码具有无限循环,则行为是不确定的,因此它是合法的.
但我认为,我们不需要标准所说的有问题的无限循环导致UB实现这一点.访问易失性对象,执行原子操作和同步操作都是"禁用"循环终止假设的副作用.
(我有一堆执行以下lambda的异步调用
auto lambda = [&] {
while (m.try_lock() == false) {
for (size_t i = 0; i < (2 << 24); i++) {
vi++;
}
vi = 0;
}
};
Run Code Online (Sandbox Code Playgroud)
并且只有在用户输入时才会释放锁定.但是还有其他有效的合法无限循环.)
如果我安排了几个这样的任务,我在他们之后安排的任务就无法运行.
一个非常邪恶的例子是启动太多任务,直到锁被释放/一个标志被引发,然后使用`std :: async(std :: launch :: async,...)计划一个引发标志的任务.除非"最终"这个词意味着非常令人惊讶,否则该程序必须终止.但是在VC++实现下它不会!
对我来说,这似乎违反了标准.令我惊讶的是这张纸条中的第二句话.因素可能会阻止实施对前进的某些保证.那么这些实现如何符合?
这就像是说可能存在阻碍实现提供内存排序,原子性甚至多个执行线程存在的某些方面的因素.很棒,但符合要求的托管实现必须支持多个线程.对他们和他们的因素太糟糕了.如果他们不能提供那些不是C++的人.
这是放宽要求吗?如果解释如此,则完全取消要求,因为它没有指明哪些因素,更重要的是,实施可能没有提供哪些保证.
如果不是 - 那注意甚至意味着什么?
我记得根据ISO/IEC指令,脚注是非规范性的,但我不确定注释.我确实在ISO/IEC指令中找到了以下内容:
24注意事项
24.1目的或理由
注释用于提供有助于理解或使用文档文本的附加信息.该文件可在没有说明的情况下使用.
强调我的.如果我认为该文档没有那些不清楚的注释,在我看来,线程必须取得进展,如果函数在新线程上执行,则std::async(std::launch::async, ...)
具有效果,如果它是使用创建的,因此使用必须派生的仿函数取得进展.而在使用线程池的VC++实现中,它们没有.所以VC++在这方面违反了标准.std::thread
std::async(std::launch::async, ...)
完整示例,在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;
}
Run Code Online (Sandbox Code Playgroud)
等于4或更少时终止.有超过4个没有.
类似的问题:
是否有使用线程池的std :: async实现? - 但它并不质疑合法性,反正也没有答案.
std :: async - 依赖于实现的用法?- 提到"线程池实际上并不受支持",但侧重于thread_local
变量(即使"不是直截了当"也可以解决,或者如答案和评论所说的那样可以解决)并且没有解决附近取得进展要求的说明.
P0296R2在 C++17 中已经对这种情况进行了一定程度的澄清。除非 Visual C++ 实现记录其线程不提供并发前进进度保证(这通常是不可取的),否则有界线程池不符合要求(在 C++17 中)。
关于“外部强加的线程优先级”的注释已被删除,也许是因为环境总是有可能阻止 C++ 程序的进度(如果不是按优先级,则通过挂起,如果不是,则通过电源)或硬件故障)。
该部分中还有一个剩余的规范性“应该”,但它仅适用于(如 conio提到的)无锁操作,无锁操作可能会因其他线程对同一高速缓存行(而不仅仅是同一原子)的频繁并发访问而无限期地延迟。多变的)。(我认为在某些实现中,即使其他线程只是读取,这种情况也可能发生。)
归档时间: |
|
查看次数: |
1801 次 |
最近记录: |