如果你想非同步地執行函式doasyncwork
,你有兩個基本的選擇。你可以建立乙個std::thread,用它來執行doasyncwork
,這是基於執行緒(thread-based)的方法:
或者你把int doasyncwork();
std::thread t(doasyncwork);
doasynwork
傳遞給std::async,乙個叫做基於任務(task-based)的策略:
auto fut = std::async(doasyncwork); // "fut"的意思是"future"
在這種呼叫中,傳遞給std::async的函式物件被認為是乙個任務(task)。
基於任務的方法通常優於基於執行緒的對應方法,我們看到基於任務的方法**量更少,這已經是展示了一些原因了。在這裡,doasyncwork
會返回乙個值,我們有理由假定呼叫doasyncwork
的**對這個返回值有興趣。在基於執行緒的呼叫中,沒有直接的辦法獲取到它;而在基於任務的呼叫中,這很容易,因為std::asyn返回的future
提供了乙個函式get來獲取返回值。如果doasyncwork
函式發出了乙個異常,get函式是很重要的,它能取到這個異常。在基於執行緒的方法中,如果doasyncwork
丟擲了異常,程式就死亡了(借助std::terminate)。
基於執行緒程式設計和基於任務程式設計的乙個更根本的區別是,基於任務程式設計表現出更高階別的抽象。它讓你擺脫執行緒管理的細節,這提醒我需要總結一下併發c++軟體裡「執行緒」的三種含義:
軟體執行緒是一種受限的資源,如果你想建立的執行緒數量多於系統提供的數量,會丟擲std::system_error異常。就算你規定函式不能丟擲異常,這個異常也會丟擲。例如,就算你把doasyncwork
宣告為noexcept,
int doasyncwork noexcept; // 關於noexcept
這語句還是可能會丟擲異常:
std::thread t(doasyncwork); // 如果沒有可獲取的系統,就丟擲異常
寫得好的軟體必須想個辦法解決這個可能性,但如何解決呢?乙個辦法是在當前執行緒執行doasyncwork
,但這會導致負載不均衡的問題,而且,如果當前執行緒是個gui執行緒,會導致響應時間變長。另乙個方法是等待某些已存在的軟體執行緒完成工作,然後再嘗試建立乙個新的std::thread物件,但是有可能發生這種事情:已存在的執行緒在等待doasyncwork
的處理(例如,doasyncworkd
的返回值,或者通知條件變數)。
即使你建立的執行緒數量沒超過系統限制,你還是會有oversubscription(過載)的問題——當就緒狀態(即非阻塞)的軟體執行緒多於硬體執行緒的時候。此時,排程執行緒(通常是作業系統的一部分)會為軟體執行緒分配cpu時間片,乙個執行緒的時間片用完,就執行另乙個執行緒,這其中發生了上下文切換。這種上下文切換會增加系統的執行緒管理開銷。這種情況下,(1)cpu快取會持有那個軟體執行緒(即,它們會儲存對於那軟體執行緒有用的一些資料和一些指令),而(2)cpu核心上「新」執行的軟體執行緒「汙染」了cpu快取上「舊的」執行緒資料(它曾經在該cpu核心執行過,且可能再次排程到該cpu核心執行)。
避免oversubscription是很困難的,因為軟體系統和硬體執行緒的最佳比例是取決於軟體執行緒多久需要執行一次,而這是會動態改變的,例如,當乙個執行緒從io消耗型轉換為cpu消耗型時。這最佳比例也取決於上下文切換的開銷和軟體執行緒使用cpu快取的效率。再進一步說,硬體執行緒的數量和cpu快取的細節(例如,快取多大和多快)是取決於機器的體系結構,所以即使你在乙個平台上讓你的應用避免了oversubscription(保持硬體繁忙工作),也不能保證在另一種機器上你的方案能工作得好。
如果你把這些問題扔給某個人去做,你的生活就很愜意啦,然後使用std::async就能顯式地做這件事:
auto fut = std::async(doasyncwork); // 執行緒管理的責任交給標準庫的實現者
這個呼叫把執行緒管理的責任轉交給c++標準庫的實現者。例如,得到執行緒數超標異常的可能性顯著減少,因為這個呼叫可能從不產生這個異常。「它是怎樣做到的呢?」你可能好奇,「如果我申請多於系統提供的執行緒數,使用std::thread和使用std::async有區別嗎?」答案是有區別,因為當用預設啟動策略(default launch policy)呼叫std::async時,不能保證它會建立乙個新的軟體執行緒。而且,它允許排程器把指定函式(本例中的doasyncwork
)執行在——請求doasyncwork
結果的執行緒中(例如,那個執行緒呼叫了get或者對fut使用wait ),如果系統oversubsrcibed或執行緒數耗盡時,合理的排程器可以利用這個優勢。
如果你想用「在需要函式結果的執行緒上執行該函式」來欺騙自己,我提起過這會導致負載均衡的問題,這問題不會消失,只是由std::async和排程器來面對它們,而不是你。但是,當涉及到負載均衡問題時,排程器比你更加了解當前機器發生了什麼,因為它管理所有程序的執行緒,而不是只是你的**執行於的程序和執行緒。
使用std::async,gui執行緒的響應性也是有問題的,因為排程器沒有辦法知道哪個執行緒具有嚴格的響應性要求。在這種情況下,你可以把std::lanuch::async啟動策略傳遞給std::async,它那可以保證你想要執行的函式馬上會在另乙個執行緒中執行。
最先進的執行緒排程器使用了系統範圍的執行緒池來避免oversubscription,而且排程器通過工作竊取(workstealing)演算法來提高了硬體核心的負載均衡能力。c++標準庫沒有要求執行緒池或者工作竊取演算法,而且,實話說,c++11併發技術的一些實現細節讓我們很難利用到它們。但是,一些**商會在它們的標準庫實現中利用這種技術,所以我們有理由期待c++併發庫會繼續進步。如果你使用基於任務的方法進行程式設計,當它以後變智慧型了,你會自動獲取到好處。另一方面,如果你直接使用std::thread進行程式設計,你要承擔著處理執行緒耗盡、oversubscription、負載均衡的壓力,更不用提你在程式中對這些問題的處理方案能否應用在同一臺機器的另乙個程序上。
比起基於執行緒程式設計,基於任務的設計能分擔你的執行緒管理之痛,而且它提供了一種很自然的方式,讓你檢查非同步執行函式的結果(即,返回值或異常)。但是,有幾種情況直接使用std::thread更適合,它們包括
不過,這些都是不常見的情況。大多數時候,你應該選擇基於任務的設計,來代替執行緒。
需要記住的3點:
基於python的多執行緒程式設計
由於之前看書的時候,對多執行緒的呼叫不怎麼了解,藉此機會,看了一些資料自己整理了一下 osi七層模型 物理層資料鏈路層 網路層傳輸層 會話層表示層 應用層協議分為tcp ip協議 現在用得最多的是這兩種協議,tcp ip,ip分管各個電腦終端位址,負責發包 包 就是資料 以塊的形式傳送,tcp負責運...
基於MFC的執行緒函式程式設計
int cnt 0 計數器 int terminal flag 0 stop標誌 cwinthread m pthread 執行緒名 static uint jisuan lpvoid pparam 執行緒函式新增的執行緒函式如下,利用this指標指向計數器cnt,顯示到edit control中,...
python基於多執行緒的TCP多工伺服器
import socket import threading def recv msg new socket while true 阻塞的接收資料 recv data new socket.recv 1024 if recv data 接到的值decode recv txt recv data.de...