技術分享:Linux多核並行程式設計關鍵技術

HitTwice發表於2018-06-13

  多核並行程式設計的背景

  在摩爾定律失效之前,提升處理器效能通過主頻提升、硬體超執行緒等技術就能滿足應用需要。隨著主頻提升慢慢接近撞上光速這道牆,摩爾定律開始逐漸失效,多核整合為處理器效能提升的主流手段。現在市面上已經很難看到單核的處理器,就是這一發展趨勢的佐證。要充分發揮多核豐富的計算資源優勢,多核下的並行程式設計就不可避免,Linux kernel就是一典型的多核並行程式設計場景。但多核下的並行程式設計卻挑戰多多。

  多核並行程式設計的挑戰

  目前主流的計算機都是馮諾依曼架構,即共享記憶體的計算模型,這種過程計算模型對平行計算並不友好。下圖是一種典型的計算機硬體體系架構。

  這種架構中,有如下設計特點:

  ·多個CPU核改善處理器的計算處理能力;

  ·多級cache改善CPU訪問主存的效率;

  ·各個CPU都有本地記憶體(NUMA(非一致性記憶體訪問)),進一步改善CPU訪問主存的效率;

  ·store buffer模組改善cache write由於應答延遲而造成的寫停頓問題;

  ·invalidate queue模組改善使無效應答的時延,把使無效命令放入queue後就立即傳送應答;

  外設DMA支援直接訪問主存,改善CPU使用效率;

  這些硬體體系設計特點也引入很多問題,最大的問題就是cache一致性問題和亂序執行問題。

  cache一致性問題由cache一致性協議MESI解決,MESI由硬體保證,對軟體來說是透明的。MESI協議保證所有CPU對單個cache line中單個變數修改的順序保持一致,但不保證不同變數的修改在所有CPU上看到的是相同順序。這就造成了亂序。不僅如此,亂序的原因還有很多:

  ·store buffer引起的延遲處理,會造成亂序;

  ·invalidate queue引起的延遲處理,會造成亂序;

  ·編譯優化,會造成亂序;

  ·分支預測、多流水線等CPU硬體優化技術,會造成亂序;

  ·外設DMA,會造成資料亂序;

  這種情況造成,就連簡單的++運算操作的原子性都無法保證。這些問題必須採用多核並行程式設計新的技術手段來解決。

  多核並行程式設計關鍵技術

  鎖技術

  Linux kernel提供了多種鎖機制,如自旋鎖、訊號量、互斥量、讀寫鎖、順序鎖等。各種鎖的簡單比較如下,具體實現和使用細節這裡就不展開了,可以參考《Linux核心設計與實現》等書的相關章節。

  ·自旋鎖,不休眠,無程式上下文切換開銷,可以用在中斷上下文和臨界區小的場合;

  ·訊號量,會休眠,支援同時多個併發體進入臨界區,可以用在可能休眠或者長的臨界區的場合;

  ·互斥量,類似與訊號量,但只支援同時只有一個併發體進入臨界區;

  ·讀寫鎖,支援讀併發,寫寫/讀寫間互斥,讀會延遲寫,對讀友好,適用讀側重場合;

  ·順序鎖,支援讀併發,寫寫/讀寫間互斥,寫會延遲讀,對寫友好,適用寫側重場合;

  鎖技術雖然能有效地提供並行執行下的競態保護,但鎖的並行可擴充套件性很差,無法充分發揮多核的效能優勢。鎖的粒度太粗會限制擴充套件性,粒度太細會導致巨大的系統開銷,而且設計難度大,容易造成死鎖。除了併發可擴充套件性差和死鎖外,鎖還會引入很多其他問題,如鎖驚群、活鎖、飢餓、不公平鎖、優先順序反轉等。不過也有一些技術手段或指導原則能解決或減輕這些問題的風險。

  ·按統一的順序使用鎖(鎖的層次),解決死鎖問題;

  ·指數後退,解決活鎖/飢餓問題;

  ·範圍鎖(樹狀鎖),解決鎖驚群問題;

  ·優先順序繼承,解決優先順序反轉問題 ;

  原子技術

  原子技術主要是解決cache不一致性和亂序執行對原子訪問的破壞問題。主要的原子原語有:

  ACCESS_ONECE():只限制編譯器對記憶體訪問的優化;

  barrier():只限制編譯器的亂序優化;

  smb_wmb():寫記憶體屏障,重新整理store buffer,同時限制編譯器和CPU的亂序優化;

  smb_rmb():讀記憶體屏障,重新整理invalidate queue,同時限制編譯器和CPU的亂序優化;

  smb_mb():讀寫記憶體屏障,同時重新整理store buffer和invalidate queue,同時限制編譯器和CPU的亂序優化;

  atomic_inc()/atomic_read()等:整型原子操作;

  多提一句的是,atomic_inc()原語為了保證原子性,需要對cache進行重新整理,而快取行在多核體系下傳播相當耗時,其多核下的並行可擴充套件性差。

  無鎖技術

  上一小節中所提到的原子技術,是無鎖技術中的一種,除此之外,無鎖技術還包括RCU、Hazard pointer等。值得一提的是,這些無鎖技術都基於記憶體屏障實現的。

  Hazard pointer主要用於物件的生命週期管理,類似引用計數,但比引用計數有更好的並行可擴充套件性;

  RCU適用的場景很多,其可以替代:讀寫鎖、引用計數、垃圾回收器、等待事物結束等,而且有更好的並行擴充套件性。但RCU也有一些不適用的場景,如寫側重;臨界區長;臨界區內休眠等場景。

  不過,所有的無鎖原語也只能解決讀端的並行可擴充套件性問題,寫端的並行可擴充套件性只能通過資料分割技術來解決。

  資料分割技術

  分割資料結構,減少共享資料,是解決並行可擴充套件性的根本辦法。對分割友好(即並行友好)的資料結構有:

  ·陣列

  ·雜湊表

  ·基樹(Radix Tree)/稀疏陣列

  ·跳躍列表(skip list)

  使用這些便於分割的資料結構,有利於我們通過資料分割來改善並行可擴充套件性。

  除了使用合適的資料結構外,合理的分割指導規則也很重要:

  ·讀寫分割:以讀為主的資料與以寫為主的資料分開;

  ·路徑分割:按獨立的程式碼執行路徑來分割資料;

  ·專項分割:把經常更新的資料繫結到指定的CPU/執行緒中;

  ·所有權分割:按CPU/執行緒個數對資料結構進行分割,把資料分割到per-cpu/per-thread中;

  4種分割規則中,所有權分割是分割最徹底的。

  以上這些多核並行程式設計內容基本上涵蓋了Linux kernel中所有的併發程式設計關鍵技術。當然並行程式設計還有很多其他技術沒有應用到Linux kernel中的,如無副作用的並行函數語言程式設計技術(Erlang/Go等)、訊息傳遞、MapReduce等等。

  本文作者:wahaha02
  原文連結:https://www.cnblogs.com/wahaha02/p/9175637.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31473948/viewspace-2156123/,如需轉載,請註明出處,否則將追究法律責任。

相關文章