本文是.Net中的並行編程第六篇,今天就介紹一些我在實際項目中的一些常用優化策略。
一、避免線程之間共享數據
避免線程之間共享數據主要是因為鎖的問題,無論什么粒度的鎖,最好的線程之間同步方式就是不加鎖,這個地方主要措施就是找出數據之間的哪個地方需要共享數據和不需要共享數據的地方,再設計上避免多線程之間共享數據。
在以前做過的某項目,開始時設計的方案:
開始設計時所有的數據都放入到了公共隊列,然后隊列通知多個線程去處理數據,隊列采用互斥鎖保證線程同步,造成的結果就是線程進行頻繁的上下切換,CPU時間都浪費在了上下文切換上從而導致隊列擁堵無法及時處理數據,最終程序因無法處理數據而內存溢出。
一開始解決這個問題是想到了采用更細粒度的鎖如原子操作,但使用原子操作又需要回退機制保證活鎖問題還要防止浪費CPU的時間等等一系列的問題。。,如果開發完成其實就相當於自己實現了一個如lock之類的互斥鎖。所以重新整理了業務需求,改良后的設計如下:
在數據輸入進來以后采用一個單獨的數據轉發器分發到不同的隊列,這樣就避免了線程之間的競爭,數據轉發器其實就相當於我們開發網站時用到了負載均衡器,而且可以根據隊列里面的數據不同選擇不用的隊列或者可以結合CPU,內存使用率進行動態調度。當然改良后的設計滿足了性能需求。
所以在進行多線程開發時,盡量避免使用鎖,對於必須使用鎖的情景要選擇合適的鎖,對於選擇什么類型的鎖,我的原則是:能滿足性能要求就好,不要刻意追求細粒度的鎖。粗粒度的鎖性能低但易於使用和理解,細粒度的鎖性能高但難以使用和理解,關於操作系統的鎖的介紹可參考《windows核心編程》線程同步的章節,在.net平台下也可參考《CLR VIA C#》線程相關章節介紹。
二、注意CPU高速緩存失效,避免頻繁的上下文切換。
開發多核程序時高速緩存往往是提高性能的關鍵,這里的緩存指的是CPU的緩存(L1,L2,L3)。緩存運用得當往往能提高2倍以上的性能。
1.造成CPU緩存失效的原因有:
(1)頻繁的修改內存中的數據
(2)使用了原子操作,lock鎖等同步方式。
(3)線程上下文的切換。
(4)偽共享造成頻繁的刷新高速緩存。
2. 關於頻繁的上下文切換造成的原因有:
(1)程序本身的線程都在搶奪CPU資源,也就是CPU無法調度其他的線程,
(2)很多線程都在等待獲取互斥鎖但是只有一個線程能獲取,其他線程不斷在喚醒和睡眠之間切換。
3.以上問題的解決方案(只供參考,項目不同方案不同):
(1)避免使用任何形式的鎖。
(2)在滿足性能的前提下,用最少的線程做最少的事。
(3)再設計上避免修改數據,如以前開發的實時計算的程序,所有的數據只允許讀取不允許修改,需要修改則創建一條新數據來代替,類似於Erlang程序的開發方式。
具體有關高速緩存基礎內容可以參考《深入理解計算機操作系統》第六章的內容。
三、線程池的的使用場景和注意點:
(1)對於執行時間較短的任務都應當交給線程池去處理而不是開啟一個新線程,對於需要長時間處理的任務交給單獨的線程去處理。
(2)對於讀寫文件的任務不要交給線程池處理,因為線程池內的線程屬於后台線程,應用程序意外關閉后線程也關閉從而丟失數據。
(3)永遠不要自己開發線程池,真正能用到產品級別線程池需要幾個月的時間而且需要大量的測試,否則遇到問題悔之晚矣。
四、關於NUMA架構機器的優化。
(1)現代服務器基本都是NUMA架構,在這些機器上我們編寫程序時最好開啟.net的服務端垃圾回收模式,這樣我們分配對象時就能在最近使用CPU對應的內存上去分配。
(2)不要在.net程序中使用綁定線程到指定內核或提升線程優先級的做法,因為如果綁定的線程正好與垃圾回收線程進行競爭那么性能會更慢。
五、選擇合適的編程模型
編寫並行程序都有固定的編程模型,基本上其他模型都是這幾種模型的自由組合,常見的編程模型:
(1)數據並行
(2)任務並行
(3)流水線並行
六、去隊列里拉數據還是隊列主動推送數據?
一般我們寫多線程的程序會將數據先放到隊列中,然后函數立即返回,后續再有其他的線程進行處理以達到快速響應客戶端的目的,這就涉及到隊列主動發送信號通知線程處理還是線程定時去隊列里拉取數據,如果采用推送的方式可能造成狀態丟失最終有些數據得不到處理而一直待在隊列中,如果采用拉取的方式線程的睡眠時間不好把握,睡多了數據處理速度慢,睡少了又浪費CPU,所以我一般采用兩者結合的方式,具體參考我的第四篇文章《.Net中的並行編程-4.實現高性能異步隊列》
七、異步IO還是同步IO?
異步IO可以解決同步IO的線程阻塞問題(這里的IO分兩種:磁盤和網絡),基本上所有的web服務器都采用的異步的網絡IO,但是對於磁盤最好不要使用異步的磁盤IO,除非在具有SSD的機器上。因為對磁盤異步的讀寫會造成磁盤存儲數據時的碎片化,本來可以順序寫的操作最終可能變成隨機寫。
結束語:
本文設計的內容只是並行程序優化很少一部分,更多內容還需要我們在實踐中不斷積累。
本來想寫的內容是”.net並行“,結果寫完才發現和.net沒有關系,當然,這些基礎知識和語言沒什么太大關系。
本文設計的內容只是建議,我們在編寫程序時不要具有教條主義,合理的才是最好的,某些原則不是適合所有情況,我們所做的是不斷探索適應變化以達到提升程序性能的目的。