1. 程式人生 > >併發資料結構-1.1.2 阻塞技術

併發資料結構-1.1.2 阻塞技術

原文連結譯文連結,譯者:周可人,校對:樑海艦

1.1.2 阻塞技術

在很多資料結構中,記憶體競爭所帶來的不良現象和前文所說的順序瓶頸帶來的影響都可以通過使用細粒度鎖機制來減小。在細粒度鎖機制中,我們用多個粒度較小的鎖來保護資料結構中的不同部分。這樣做的目的是允許併發操作在它們不訪問資料結構的相同部分時並行執行。這種方法也可以用於避免獨立記憶體位置訪問的額外競爭。在一些資料結構中,這種現象經常發生;舉個例子,在雜湊表中,對那些被雜湊到不同雜湊桶中的值的操作自然訪問的是資料結構中的一部分。

對其他的資料結構,如基於鎖的共享計數器,怎樣減少競爭和降低順序瓶頸並沒有那麼清晰,因為抽象地來說,所有的操作都對資料結構的同一部分做修改。一種處理競爭的方法是將所有操作分散在不同時間,從而使每一個操作在不同的時段訪問計數器。一種廣泛使用的處理技術被稱為回退。然而,即使減少了競爭,我們的基於鎖的計數器仍然缺少並行性,並且不可擴充套件。幸運的是,更加細緻的技術可以提升可擴充套件性。

一種技術,被稱為合併樹,可以用來實現一個可擴充套件的計數器。這種技術使用了一個二叉樹,每個葉子表示一個執行緒。樹的根節點儲存實際的計數器值,樹的其他內部節點用來協調對根節點的訪問。其中的核心思想是執行緒從樹的葉子節點向上爬,嘗試去和其他併發操作合併。每一次兩個執行緒的操作在一個內部節點合併,其中一個執行緒(失敗者),簡單地在當前節點等待,直到一個返回值傳遞給它。另外一個執行緒(勝利者),朝著根節點向上,攜帶所有在該節點子樹中合併的操作之和;一個到達根節點的勝利者執行緒將它的和增加到計數器中,因此所有合併的操作增長被加到計數器中。之後這個勝利者在樹中下降,分發一個返回值給每一個之前合併的,處在等待狀態的失敗者執行緒。這些返回值被分發下來,這樣的效果就好像所有增長操作都在根計數器被修改的時刻一個接著一個的執行。

在合併樹中,失敗者在等待勝利者時所使用的技術對效能而言是重要的。一個失敗者採用重複地讀取一個樹節點的一個記憶體地址的方式操作等待,這被稱為自旋。在快取一致性多核處理器中,一個重要的推論是這個位置會位於執行失敗者操作的處理器的本地快取,直到勝利者操作報告結果。這意味著等待的失敗者並沒有產生任何不必要的,並且可能降低勝利者效能的記憶體流量。這種等待被稱為本地自旋,且已經被證實對提升可擴充套件性來說至關重要。

在所謂的非一致性記憶體訪問(NUMA)架構中,處理器訪問他們的共享儲存中本地儲存部分要比訪問其他處理器的部分要快的多。在這樣的架構中,資料佈局——合併樹中節點在記憶體中的分佈方式——對效能有著顯著的影響。將樹的葉子節點存放在處理相應執行緒的處理器附近可以提升效能。(我們假設執行緒是和處理器靜態繫結的)

資料佈局的問題也對快取一致性多核處理器上併發資料結構的設計有影響。回顧合併樹的一個作用是減少獨立記憶體位置的競爭,從而提升效能。然而,因為快取一致性多核處理器用快取行大小的塊管理記憶體,如果兩個執行緒訪問不同記憶體區域,落在了相同的快取行中,會和他們訪問同一塊記憶體地址一樣受到效能影響。這種現象被稱為偽共享,這是一個常見的,令人困惑的效能問題。

在減少獨立記憶體位置的競爭,用本地自旋減少記憶體流量,允許操作並行執行之後,用合併樹實現的,隨著併發執行緒的數量擴充套件的計數器,比單鎖版本的計數器要好的多。如果所有執行緒被用於不斷合併,那麼一個P寬度的樹允許P個執行緒在O(logP)個(合併樹中的)上升和下降的操作之後,返回P個值,提供了O(P/logP)的加速比。

儘管使用合併樹的方式有很多優點,但是它也有一些不足之處。合併樹需要限定P個執行緒訪問計數器,並且需要O(P)的空間。雖然它在高負載下能提供更高的吞吐量,即在樹被大量執行緒訪問時,但它在低負載訪問時的最好效能是差的:它必須遍歷樹中的O(logP)個節點,然而一個基於單鎖的fetch-and-inc操作在常數時間內可以完成。此外,如果一個執行緒因為它在一個勝利者執行緒離開樹向上後馬上到達導致合併失敗,那麼它必須等待,直到勝利者返回才能繼續向上。如果上升的勝利者,失敗者,以及之後的上升執行緒之間的協調處理錯誤,那麼可能會導致死鎖:執行緒可能以迴圈的方式互相阻塞,沒有一個可以繼續執行。避免死鎖顯著地增加了設計正確,並且高效的阻塞併發資料結構的複雜性。

總的來說,如果在使用足夠的阻塞來達到正確,和儘量降低阻塞來允許併發操作並行執行之間達到平衡,阻塞資料結構可以提供強大的,高效的實現。