天天看點

如何提高多線程程式的cpu使用率

正如大家所知道的那樣,多核多cpu越來越普遍了,而且編寫多線程程式也是件很簡單的事情。在Windows下面,調用CreateThread函數一次就能夠以你想要的函數位址建立一個子線程運作。然後,事情确實你發現建立多線程根本沒有讓程式快多少,也沒有提高多少cpu使用率,甚至可能讓cpu使用率下降。唯一能夠确定的是多線程能夠避免界面假死。為什麼會是這樣的了。本文将舉一些例子和講述一些原因。

  首先,我來講一下多處理的一些知識。如下圖所示,

如何提高多線程程式的cpu使用率

  多處理器系統也隻有一個待運作的線程隊列,記憶體中也隻有一個作業系統拷貝,而且也隻有一個記憶體系統,但是會有多個cpu同時運作不同的線程。一個cpu運作一個線程,那麼上圖中的系統最多能在同一時間運作2個線程。其實,多處理系統需要掌握的知識不是這些,而是緩存一緻性。

  現在來解釋下什麼是緩存一緻性。由于,還是隻有一個記憶體系統。所有cpu都要和這個記憶體系統通信,但是隻有一條總線,那麼這無疑會造成總線緊張,限制整體的速度了。那麼,你多個cpu也沒多少意義了。解決這個問題的辦法還是利用cpu的緩存機制,學過組成原理的同學都知道,cpu的緩存命中率還是很高的,有90%以上吧。那麼,我繼續利用緩存機制還是可以降低總線的頻繁使用的。但是,每個cpu都有自己的緩存。如果有2個cpu的緩存存儲的是同一記憶體資料的内容,其中一個cpu的緩存更新了,另外一個cpu的緩存也必須更新,這就是所謂的緩存一緻性。程式設計多線程程式的一個很重要的一點就是避免因為緩存一緻性引起的緩存更新風暴。

  現在我舉一個緩存更新風暴的例子。

如圖所示的類定義,

如何提高多線程程式的cpu使用率

  鎖lockHttp和lockSsl中間隻有8個位元組,而絕大部分系統上一個緩存行是128個位元組,那麼這2個鎖很可能就處在同一個緩存行上面。那麼,最壞的情況會發生什麼事情了。假設處理器P1在運作一個處理http請求的線程T1,處理器P2在運作一個處理ssl請求的線程T2,那麼當T1獲得鎖lockHttp的時候,鎖的内容就會改變,為了保持緩存一緻性,就會更新P2的緩存。那麼,T2要獲得鎖lockssl的時候,發現緩存已經失效了,就必須從記憶體中重新加載緩存之類。總之,這會将緩存命中率降低到90%以下,引起性能的嚴重降低。而且發生這種事情的原因是因為我們不了解硬體的體系結構。

  多cpu不能成倍提高速度的原因是任務的某些部分是必須串行處理的。比如,矩陣乘法可以分為三個部分,初始化矩陣,相乘,傳回結果。這三部分第二部分可以用多線程來處理,第一部分和第三部分則是不可以的。而且第二部分必須在第一部分完成之後,第三部分必須在第一部分完成之後。那麼,無論你添加多少個處理器,最快的時間都至少是第一部分和第二部分的時間之和。這個事實好像叫做Amdahl法則。

  如果使用多線程,那麼就必須考慮線程同步,而線程同步又是導緻速度降低的關鍵。是以下面就講述一些方法來加快多線程程式的吞吐速度。

  方法一,把一個任務分解為多個可以子任務。

  因為總有些子任務是可以并發的,多個子任務并發執行了很可能就能夠避免cpu需要io操作的完成了,而且能夠提高系統的吞吐量。

  方法二,緩存多線程的共享資料。

  當你已經在使用多線程了,很多時候必須使用共享資料。如果,資料是隻讀的,那麼可以在第一次擷取後儲存起來,以後就可以重複使用了。但是,第一次的擷取還是無法避免的需要線程同步操作的。

  方法三,如果線程數目有限,就不要共享資料。

  做法是為每一個線程執行個體化一個單獨的資料,其實就是為每一個線程配置設定一塊資料使用。這樣沒有線程同步操作了,速度可以盡可能的提示。

  方法四,如果沒辦法确定線程數目到底有多少,那麼使用部分共享吧。

  部分共享其實就是使用多個資源池代替一個資源池,資源池的數目得更加經驗來确定。如下圖所示,

如何提高多線程程式的cpu使用率

  到現在我們知道了為什麼多cpu并不能成倍提高程式的速度了。首先因為有些任務無法并行,其次即使是并行cpu之間還是有很多牽制的。本書的内容主要來自提高c++性能的程式設計技術一書。

繼續閱讀