[英]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::async
和std::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個沒有。
類似的問題:
是否有使用線程池的std :: async實現? - 但它並不質疑合法性,反正也沒有答案。
std :: async - 依賴於實現的用法? - 提到“線程池實際上並不受支持”,但側重於thread_local
變量(即使“不直截了當”也可以解決,或者如答案和評論所說的那樣可以解決)並且沒有解決附近進展要求的說明。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.