乙個執行在p個處理上的應用程式的加速度是它在單個處理器上的執行時間和在p個處理器的執行時間的比值。這是一種評價應用程式對於機器資源利用程度的衡量。理想情況下,我們想要的結果是線性加速度:當我們使用p個處理器的時候,我們希望可以獲得p的加速度(譯者注:例如乙個應用程式在單處理器的執行時間是10秒,那麼在雙處理的執行時間理想情況下是5秒)。加速度隨著p一起增加的資料結構我們稱之為可擴充套件的資料結構。在設計可擴充套件的資料結構的時候,我們必須注意:為了同步使用簡單的方法會嚴重破壞擴充套件性。
回到基於鎖的計數器,我們會發現鎖會帶來順序性的瓶頸:在任何時間點,最多只有乙個fetch-and-inc操作是有用的,例如:遞增變數x。這種順序性的瓶頸會對本來能夠到達的加速度造成令人驚訝的影響。順序執行部分**對效能帶來的影響已經在基於amdahl』s law的乙個簡單公式中闡明了。假設b是乙個程式中順序性瓶頸所佔的百分比。假設在單處理器中執行這個程式需要1個時間單位,那麼在p個處理器的環境中,執行順序執行部分的**需要b個時間單位,同時在最好的情況下,併發部分的**消耗(1-b)/p個時間單位,那麼加速度最多等於 1/(b+(1-b)/p) 。這意味中如果程式中只有10%是屬於順序性瓶頸的部分,那麼在乙個10個處理器的環境中,程式能達到的加速度最多只有5.3:我們的應用只利用了機器的一半效能。減少順序性執行**塊的數量和長度對效能是至關重要的。在基於鎖的上下文中,這意味著減少申請鎖的次數,降低鎖的粒度:一種用來表示在持有鎖時,執行指令數目的衡量。
我們的簡單計數器實現碰到的第二個問題是記憶體競爭:這是底層硬體為了多執行緒併發嘗試訪問相同的記憶體位址所造成的流量的開銷。只有當你理解普通的共享記憶體多處理器架構一些方面,你才能意識到記憶體競爭。如果保護著我們計數器的鎖就像很多簡單鎖一樣,是使用單一位址實現的話,那麼為了請求鎖,執行緒必須重複嘗試修改這個位址。以快取一致性多處理器為例,包含著鎖的cache line的獨佔所有權必須重複的從乙個處理器傳輸到別的處理器上,這會導致每次嘗試修改記憶體位址的操作都需要長時間的等待,失敗的嘗試獲取鎖的操作會進一步加重相應的記憶體流量。在1.1.7中我們會討論針對不同型別的共享記憶體架構實現相應的鎖,避免類似這樣的問題.
基於鎖的實現的計數器的第三個問題是:當持有鎖的執行緒被延期了,那麼所有嘗試獲取鎖的執行緒都會被延期。這種現象稱為阻塞,阻塞是多道程式設計(multiprogrammed)中特有的問題,每個處理器都有多個執行緒,作業系統會給擁有鎖的執行緒優先權。對於很多的資料結構來說,這個問題可以通過設計非阻塞的演算法來解決,在非阻塞演算法中乙個執行緒的延期不會導致其他執行緒的延期。根據定義而言,這些演算法不能使用鎖。
下面我們繼續討論我們的共享計數器的例子,分別討論阻塞和非阻塞技術;我們會引入更多和效能相關的話題.
文章**
併發程式設計網-ifeve.com
併發資料結構 1 1 1 效能
乙個執行在p個處理上的應用程式的加速度是它在單個處理器上的執行時間和在p個處理器的執行時間的比值。這是一種評價應用程式對於機器資源利用程度的衡量。理想情況下,我們想要的結果是線性加速度 當我們使用p個處理器的時候,我們希望可以獲得p的加速度 譯者注 例如乙個應用程式在單處理器的執行時間是10秒,那麼...
基於鎖的併發資料結構
可以使用細粒度的鎖來減小佇列的臨界區,這裡使用了乙個dummy node用來進一步減小鎖的臨界區。若要判斷佇列是否為空,只需要執行下述判斷 head.get get tail 請注意,因為在進行push的時候需要修改tail,所以對tail的訪問和修改都需要進行加鎖。這裡使用get tail來封裝這...
java併發程式設計學習12 併發資料結構簡介
序列資料結構在併發環境下是不安全的,而直接使用鎖又會帶來效能的影響,所以jdk專門設計了針對併發環境下的資料結構,其中使用了無鎖運算來保證效能。1.可以直接使用collections.synchronizedlist 將乙個非執行緒安全的list變成支援同步的list.但是這樣做有乙個問題,就是所有...