简体   繁体   English

使用线程池合法的std :: async的Visual C ++实现

[英]Is the Visual C++ implementation of std::async using a thread pool legal

Visual C++ uses the Windows thread pool (Vista's CreateThreadpoolWork if available and QueueUserWorkItem if not) when calling std::async with std::launch::async . 当使用std::launch::async调用std::async时,Visual C ++使用Windows线程池(如果可用,则使用Vista的CreateThreadpoolWork如果不可用,则使用QueueUserWorkItem )。

The number of threads in the pool is limited. 池中的线程数是有限的。 If create several tasks that run for a long time without sleeping (including doing I/O), the upcoming tasks in the queue won't get a chance to work. 如果创建多个运行很长时间而没有休眠的任务(包括执行I / O),则队列中即将发生的任务将无法工作。

The standard (I'm using N4140) says that using std::async with std::launch::async 标准(我正在使用N4140)说使用std::asyncstd::launch::async

... calls INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...) (20.9.2, 30.3.1.2) 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 . ...调用INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...) (20.9.2,30.3.1.2) ,就像在一个由线程对象表示的新执行线程 ,在调用async的线程中对DECAY_COPY()的调用进行评估。

(§30.6.8p3, Emphasis mine.) (§30.6.8p3,强调我的。)

std::thread 's constructor creates a new thread etc. std::thread的构造函数创建一个新线程等。

About threads in general it says (§1.10p3): 关于一般的线程,它说(§1.10p3):

Implementations should ensure that all unblocked threads eventually make progress. 实现应确保所有未阻塞的线程最终取得进展。 [ Note: Standard library functions may silently block on I/O or locks. [ 注意:标准库函数可能会静默阻塞I / O或锁定。 Factors in the execution environment, including externally-imposed thread priorities, may prevent an implementation from making certain guarantees of forward progress. 执行环境中的因素(包括外部强加的线程优先级)可能会阻止实现对前进进度做出某些保证。 end note ] - 结束说明 ]

If I create a bunch of OS threads or std::thread s, all performing some very long (perhaps infinite) tasks, they'll all be scheduled (at least on Windows; without messing with priorities, affinities, etc.). 如果我创建了一堆操作系统线程或者std::thread ,它们都执行一些非常长的(也许是无限的)任务,它们都将被安排(至少在Windows上;不会弄乱优先级,亲和力等)。 If we schedule the same tasks to the Windows thread pool (or use std::async(std::launch::async, ...) which does that), the later scheduled tasks won't run until the earlier tasks will finish. 如果我们将相同的任务安排到Windows线程池(或使用std::async(std::launch::async, ...)这样做),以后的计划任务将不会运行,直到先前的任务完成。

Is this legal, strictly speaking? 严格来说,这是合法的吗? And what does "eventually" mean? 什么“最终”意味着什么?


The problem is that if the tasks scheduled first are de-facto infinite, the rest of the tasks won't run. 问题是如果首先安排的任务事实上是无限的,那么剩下的任务就不会运行。 So the other threads (not OS threads, but "C++-threads" according to the as-if rule) won't make progress. 所以其他线程(不是OS线程,但根据as-if规则的“C ++ - 线程”)将无法取得进展。

One may argue that if the code has infinite loops the behavior is undefined, and thus it's legal. 有人可能会争辩说,如果代码具有无限循环,则行为是不确定的,因此它是合法的。

But I argue that we don't need an infinite loop of the problematic kind the standard says causes UB to make that happen. 但我认为,我们不需要标准所说的有问题的无限循环导致UB实现这一点。 Accessing volatile objects, performing atomic operation and synchronization operations are all side effects that "disable" the assumption about loops terminating. 访问易失性对象,执行原子操作和同步操作都是“禁用”循环终止假设的副作用。

(I have a bunch of async calls executing the following lambda (我有一堆执行以下lambda的异步调用

auto lambda = [&] {
    while (m.try_lock() == false) {
        for (size_t i = 0; i < (2 << 24); i++) {
            vi++;
        }
        vi = 0;
    }
};

and the lock is released only upon user input. 并且只有在用户输入时才会释放锁定。 But there are other valid kinds of legitimate infinite loops.) 但是还有其他有效的合法无限循环。)

If I schedule a couple of such tasks, tasks I schedule after them don't get to run. 如果我安排了几个这样的任务,我在他们之后安排的任务就无法运行。

A really wicked example would be launching too many tasks that run until a lock is release/a flag is raised and then schedule using `std::async(std::launch::async, ...) a task that raises the flag. 一个非常邪恶的例子是启动太多任务,直到锁被释放/一个标志被引发,然后使用`std :: async(std :: launch :: async,...)计划一个引发标志的任务。 Unless the word "eventually" means something very surprising, this program has to terminate. 除非“最终”这个词意味着非常令人惊讶,否则该程序必须终止。 But under the VC++ implementation it won't! 但是在VC ++实现下它不会!

To me it seems like a violation of the standard. 对我来说,这似乎违反了标准。 What makes me wonder is the second sentence in the note. 令我惊讶的是这张纸条中的第二句话。 Factors may prevent implementations from making certain guarantees of forward progress. 因素可能会阻止实施对前进的某些保证。 So how are these implementation conforming? 那么这些实现如何符合?

It's like saying there may be factors preventing implementations from providing certain aspect of memory ordering, atomicity, or even the existence of multiple threads of execution. 这就像是说可能存在阻碍实现提供内存排序,原子性甚至多个执行线程存在的某些方面的因素。 Great, but conforming hosted implementations must support multiple threads. 很棒,但符合要求的托管实现必须支持多个线程。 Too bad for them and their factors. 对他们和他们的因素太糟糕了。 If they can't provide them that's not C++. 如果他们不能提供那些不是C ++的人。

Is this a relaxation of the requirement? 这是放宽要求吗? If interpreting so, it's a complete withdrawal of the requirement, since it doesn't specify what are the factors and, more importantly, which guarantees may be not supplied by the implementations. 如果解释如此,则完全取消要求,因为它没有指明哪些因素,更重要的是,实施可能没有提供哪些保证。

If not - what does that note even mean? 如果不是 - 那注意甚至意味着什么?

I recall footnotes being non-normative according to the ISO/IEC Directives, but I'm not sure about notes. 我记得根据ISO / IEC指令,脚注是非规范性的,但我不确定注释。 I did find in the ISO/IEC directives the following: 我确实在ISO / IEC指令中找到了以下内容:

24 Notes 24注意事项

24.1 Purpose or rationale 24.1目的或理由

Notes are used for giving additional information intended to assist the understanding or use of the text of the document. 注释用于提供有助于理解或使用文档文本的附加信息。 The document shall be usable without the notes. 该文件可在没有说明的情况下使用。

Emphasis mine. 强调我的。 If I consider the document without that unclear note, seems to me like threads must make progress, std::async(std::launch::async, ...) has the effect as-if the functor is execute on a new thread, as-if it was being created using std::thread , and thus a functors dispatched using std::async(std::launch::async, ...) must make progress. 如果我认为文档没有那个不清楚的注释,在我看来线程必须取得进展, std::async(std::launch::async, ...)具有效果- 如果函数在新线程上执行,因为它是使用std::thread创建的,因此使用std::async(std::launch::async, ...)分派的仿函数必须取得进展。 And in the VC++ implementation with the threadpool they don't. 而在使用线程池的VC ++实现中,它们没有。 So VC++ is in violation of the standard in this respect. 所以VC ++在这方面违反了标准。


Full example, tested using VS 2015U3 on Windows 10 Enterprise 1607 on i5-6440HQ: 完整示例,在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;
}

With 4 or less it terminates. 等于4或更少时终止。 With more than 4 it doesn't. 有超过4个没有。


Similar questions: 类似的问题:

The situation has been clarified somewhat in C++17 by P0296R2 . P0296R2在C ++ 17中对此情况进行了一些阐述 Unless the Visual C++ implementation documents that its threads do not provide concurrent forward progress guarantees (which would be generally undesirable), the bounded thread pool is not conforming (in C++17). 除非Visual C ++实现证明其线程不提供并发的前向进程保证 (这通常是不合需要的),否则有界线程池不符合(在C ++ 17中)。

The note about "externally imposed thread priorities" has been removed, perhaps because it is already always possible for the environment to prevent the progress of a C++ program (if not by priority, then by being suspended, and if not that, then by power or hardware failure). 关于“外部强加的线程优先级”的说明已被删除,可能是因为环境总是可能阻止C ++程序的进展(如果不是优先级,那么暂停,如果不是那样,那么通过权力或硬件故障)。

There is one remaining normative "should" in that section, but it pertains (as conio mentioned ) only to lock-free operations, which can be delayed indefinitely by frequent concurrent access by other thread to the same cache line (not merely the same atomic variable). 在该部分中还有一个规范的“应该”,但它(仅作为conio 提到 )仅涉及无锁操作,可以通过其他线程频繁并发访问同一缓存行 (不仅仅是相同的原子)而无限延迟变量)。 (I think that in some implementations this can happen even if the other threads are only reading.) (我认为在某些实现中,即使其他线程只是在读取,也会发生这种情况。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM