標籤:共用 計時 運行 平台 exec latch 完成 電腦 舉例
電腦的高速發展,在多核技術上要遠遠快於提升單核的計算能力。因而設計並發的程式成為提高軟體效能的一大利器。
並發的程式雖然可以有效利用硬體資源,但同時也會增加程式設計的難度,其首要解決的就是同步的問題。
同步問題歸納而言就是要解決兩個問題:活性失敗(liveness failure)和 安全性失敗(safety failture)。
- 活性失敗是指,線程A操作的變數c,線上程B中要訪問的時候,不是最新的線程A操作賦值後的值。產生此類問題的原因在於現代CPU多採用了快取,快取變成了CPU和記憶體的中間橋樑,資料的過渡器,而CPU對快取中的資料的修改並不會第一時間重新整理到公用的記憶體中;多個線程運行在不同的CPU的情況下,就有可能出現讀取的資料的不新鮮,導致活性失敗。
- 安全性失敗,舉例來說,線程A在調用某個計算方法, 運算過程處於中間狀態時,有參與運算的變數被其他線程修改了值,導致了線程A的這次運算結果錯誤。這裡的問題在於,線程A的這次運算不是一個原子操作,無法保證在運算的整體過程中資料的可控性。此類問題被稱作安全性失敗。
解決的辦法歸納起來也是兩大類
- 程式設計時,盡量減少跨線程的資料互動,盡量設計可重新進入的計算方法。
- 使用各種程式設計語言提供的同步機制,保證資料在多個線程之間的正確同步。
對於第一點來說,無論你是用哪種語言,都是一樣的。
那麼,針對第二點,讓我們來看一下C#和Java在處理同步上的一些大同小異。
解決活性失敗,C#和Java都提供了volatile這個關鍵字來修飾變數。這個關鍵字可以讓程式運行時對被修飾的變數無條件的在快取和主記憶體中實現資料同步。使用這個關鍵字可以解決資料不新鮮的問題,但是切記不可亂用,因為它會帶來額外的效能開銷,讓快取變得沒有意義。
解決活性失敗,當然也可以使用各種同步機制,這些同步機制也可以讓需要在一起完成的操作不被其他線程打擾,成為原子操作,從而解決安全性失敗問題。
同步大體來說可以分為兩種
- 互斥同步。互斥同步是指,線程A在訪問某個競爭資源的時候,其他線程不能訪問這個資源而被阻塞。這種方案帶來的問題是比較大的效能開銷用於線程阻塞和喚醒。這種同步機制其實是一種悲觀的同步方案,在操作開始前就假設會有其他線程來搶資源而上鎖了。
- 非阻塞同步。這種同步機制是藉助了操作和衝突檢測的硬體指令實現的原子操作,實現的樂觀同步機制。通俗的說,就是先進行操作,如果沒有其他線程在徵用共用資料,那操作就成功了,如果產生了衝突,那就不斷重試,直到資源被釋放。非阻塞同步不會讓線程掛起,不需要被喚醒,所以如果在共用資源被短期暫用的情況下,比互斥(阻塞)同步擁有更好的效能。
在C#和Java中,比較典型的非阻塞同步機制就是自旋鎖。
而同步機制的實現機制則是五花八門。
由於C#主要應用的平台在於windows,所以基本上它的同步機制都是基於windows的一些同步原語,包括事件,互斥鎖,訊號量,監視器;當然也有最佳化後的讀寫鎖,瘦鎖等等。
Java由於是跨平台的,所以提供的同步機制都需要jvm支援。可供選擇的同步機制和封裝有synchronized, ReentrantLock,CountDownLatch, CyclicBarrier, DelayQueue, PriorityBlockngQueue, ScheduledExecutor, Semaphore, Exchanger.
後續我們單獨對每種實現進行相應的比較。
C#和Java之比較(並發同步概述)