淺談多核CPU、多執行緒與平行計算
0.前言
最近發覺自己部落格轉帖的太多,於是決定自己寫一個原創的。筆者用過MPI和C#執行緒池,參加過比賽,有所感受,將近一年來,對多執行緒程式設計興趣一直不減,一直有所關注,決定寫篇文章,算是對知識的總結吧。有說的不對的地方,歡迎各位大哥們指正:)
1.CPU發展趨勢
核心數目依舊會越來越多,依據摩爾定律,由於單個核心效能提升有著嚴重的瓶頸問題,普通的桌面PC有望在2017年末2018年初達到24核心(或者16核32執行緒),我們如何來面對這突如其來的核心數目的增加?程式設計也要與時俱進。筆者斗膽預測,CPU各個核心之間的片內匯流排將會採用4路組相連:),因為全相連太過複雜,單匯流排又不夠給力。而且應該是非對稱多核處理器,可能其中會混雜幾個DSP處理器或流處理器。
2.多執行緒與平行計算的區別
(1)多執行緒的作用不只是用作平行計算,他還有很多很有益的作用。
還在單核時代,多執行緒就有很廣泛的應用,這時候多執行緒大多用於降低阻塞(意思是類似於
while(1)
{
if(flag==1)
break;
sleep(1);
}
這樣的程式碼)帶來的CPU資源閒置,注意這裡沒有浪費CPU資源,去掉sleep(1)就是純浪費了。
阻塞在什麼時候發生呢?一般是等待IO操作(磁碟,資料庫,網路等等)。此時如果單執行緒,CPU會幹轉不幹實事(與本程式無關的事情都算不幹實事,因為執行其他程式對我來說沒意義),效率低下(針對這個程式而言),例如一個IO操作要耗時10毫秒,CPU就會被阻塞接近10毫秒,這是何等的浪費啊!要知道CPU是數著納秒過日子的。
所以這種耗時的IO操作就用一個執行緒Thread去代為執行,建立這個執行緒的函式(程式碼)部分不會被IO操作阻塞,繼續幹這個程式中其他的事情,而不是乾等待(或者去執行其他程式)。
同樣在這個單核時代,多執行緒的這個消除阻塞的作用還可以叫做“併發”,這和並行是有著本質的不同的。併發是“偽並行”,看似並行,而實際上還是一個CPU在執行一切事物,只是切換的太快,我們沒法察覺罷了。例如基於UI的程式(俗話說就是圖形介面),如果你點一個按鈕觸發的事件需要執行10秒鐘,那麼這個程式就會假死,因為程式在忙著執行,沒空搭理使用者的其他操作;而如果你把這個按鈕觸發的函式賦給一個執行緒,然後啟動執行緒去執行,那麼程式就不會假死,繼續相應使用者的其他操作。但是,隨之而來的就是執行緒的互斥和同步、死鎖等問題,詳細見有關文獻
現在是多核時代了,這種執行緒的互斥和同步問題是更加嚴峻的,單核時代大都算併發,多核時代真的就大為不同,為什麼呢?具體細節請參考有關文獻。我這裡簡單解釋一下,以前volatile型變數的使用可以解決大部分問題,例如多個執行緒共同訪問一個Flag標誌位,如果是單核併發,基本不會出問題(P.S.在什麼情況下會出問題呢?Flag有多個,或者是一個數組,這時候只能通過邏輯手段搞定這個問題了,多來幾次空轉無所謂,別出致命問題就行),因為CPU只有一個,同時訪問這個標誌位的只能有一個執行緒,而多核情況下就不太一樣了,所以僅僅volatile不太能解決問題,這就要用到具體語言,具體環境中的“訊號量”了,Mutex,Monitor,Lock等等,這些類都操作了硬體上的“關中斷”,達到“原語”效果,對臨界區的訪問不被打斷的效果,具體就不解釋了,讀者可以看看《現代作業系統》。
(2)平行計算還可以通過其他手段來獲得,而多執行緒只是其中之一。
其他手段包括:多程序(這又包括共享儲存區的和分散式多機,以及混合式的),指令級並行。
ILP(指令級並行),x86架構裡叫SMT(同時多執行緒),在MIPS架構裡與之對應的是super scalar(超標量)和亂序執行,二者有區別,但共同點都是可以達到指令級並行,這是使用者沒法控制的,不屬於程式設計範圍,只能做些有限的優化,而這有限的優化可能只屬於編譯器管轄的範疇,使用者能做的甚少。
(3)典型的適於平行計算的語言
Erlang和MPI:這兩個前者是語言,後者是C++和Fortran的擴充套件庫,效果是一樣的,利用多程序實現平行計算,Erlang是共享儲存區的,MPI是混合型的。
C#.NET4.0:新版本4.0可以用少量程式碼實現並行For迴圈,之前版本需要用很繁瑣的程式碼才能實現同樣功能。這是利用了多執行緒實現平行計算。Java和C#3.5都有執行緒池(ThreadPool),也是不錯的很好用的多執行緒管理類,可以方便高效的使用多執行緒。
CUDA,還是個初生牛犢,有很大的發展潛力,只不過就目前其應用領域很有限。其目前只能使用C語言,而且還不是C99,比較低階,不能使用函式指標。個人感覺這由於硬體上天生的侷限性(平均每個核心可用記憶體小,與系統記憶體通訊時間長),只適用於做科學計算,靜態影象處理,視訊編碼解碼,其他領域,還不如高階CPU。等以後GPU有作業系統了,能充分排程GPU資源了,GPU就可以當大神了。遊戲中的物理加速,實際上多核CPU也能很好的做到。
其他語言。。。恩。。留作將來討論。
3.執行緒越多越好嗎?什麼時候才有必要用多執行緒?
執行緒必然不是越多越好,執行緒切換也是要開銷的,當你增加一個執行緒的時候,增加的額外開銷要小於該執行緒能夠消除的阻塞時間,這才叫物有所值。
Linux自從2.6核心開始,就會把不同的執行緒交給不同的核心去處理。Windows也從NT.4.0開始支援這一特性。
什麼時候該使用多執行緒呢?這要分四種情況討論:
a.多核CPU——計算密集型任務。此時要儘量使用多執行緒,可以提高任務執行效率,例如加密解密,資料壓縮解壓縮(視訊、音訊、普通資料),否則只能使一個核心滿載,而其他核心閒置。
b.單核CPU——計算密集型任務。此時的任務已經把CPU資源100%消耗了,就沒必要也不可能使用多執行緒來提高計算效率了;相反,如果要做人機互動,最好還是要用多執行緒,避免使用者沒法對計算機進行操作。
c.單核CPU——IO密集型任務,使用多執行緒還是為了人機互動方便,
d.多核CPU——IO密集型任務,這就更不用說了,跟單核時候原因一樣。
4.程式設計師需要掌握的技巧/技術
(1)減少序列化的程式碼用以提高效率。這是廢話。
(2)單一的共享資料分佈化:把一個數據複製很多份,讓不同執行緒可以同時訪問。
(3)負載均衡,分為靜態的和動態的兩種。具體的參見有關文獻。