簡體   English   中英

使用線程池合法的std :: async的Visual C ++實現

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

當使用std::launch::async調用std::async時,Visual C ++使用Windows線程池(如果可用,則使用Vista的CreateThreadpoolWork如果不可用,則使用QueueUserWorkItem )。

池中的線程數是有限的。 如果創建多個運行很長時間而沒有休眠的任務(包括執行I / O),則隊列中即將發生的任務將無法工作。

標准(我正在使用N4140)說使用std::asyncstd::launch::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,強調我的。)

std::thread的構造函數創建一個新線程等。

關於一般的線程,它說(§1.10p3):

實現應確保所有未阻塞的線程最終取得進展。 [ 注意:標准庫函數可能會靜默阻塞I / O或鎖定。 執行環境中的因素(包括外部強加的線程優先級)可能會阻止實現對前進進度做出某些保證。 - 結束說明 ]

如果我創建了一堆操作系統線程或者std::thread ,它們都執行一些非常長的(也許是無限的)任務,它們都將被安排(至少在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;
    }
};

並且只有在用戶輸入時才會釋放鎖定。 但是還有其他有效的合法無限循環。)

如果我安排了幾個這樣的任務,我在他們之后安排的任務就無法運行。

一個非常邪惡的例子是啟動太多任務,直到鎖被釋放/一個標志被引發,然后使用`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, ...)分派的仿函數必須取得進展。 而在使用線程池的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個沒有。


類似的問題:

P0296R2在C ++ 17中對此情況進行了一些闡述 除非Visual C ++實現證明其線程不提供並發的前向進程保證 (這通常是不合需要的),否則有界線程池不符合(在C ++ 17中)。

關於“外部強加的線程優先級”的說明已被刪除,可能是因為環境總是可能阻止C ++程序的進展(如果不是優先級,那么暫停,如果不是那樣,那么通過權力或硬件故障)。

在該部分中還有一個規范的“應該”,但它(僅作為conio 提到 )僅涉及無鎖操作,可以通過其他線程頻繁並發訪問同一緩存行 (不僅僅是相同的原子)而無限延遲變量)。 (我認為在某些實現中,即使其他線程只是在讀取,也會發生這種情況。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM