併發資料結構-1.4 池
實現高效併發棧和佇列的大部分挑戰來自於一個被插入的元素可以被刪除這一需求。併發池是一種支援插入和刪除操作的資料結構,它允許刪除操作移除任何一個已經被插入的,並且沒有在隨後被刪除的元素。這樣的弱需求提供了提高併發效能的機會。
一個高效的併發池可以使用任意靜態一致的計數器來構建。在這樣的併發池中,元素被置於陣列當中,fetch-and-inc操作決定插入操作在哪個位置儲存元素,同樣的,fetch-and-inc操作決定刪除操作在哪個位置獲得元素。每一個數組元素都包含了一個表示滿/空的位元位或者等效的機制,來表明在相應的位置,將要刪除的元素已經存在。在這種策略下,使用combining tree,combining funnel,counting network,diffracting tree中的任何一個技術都可以並行化共享技術器,解決這一主要的瓶頸來創建出一個高吞吐量的共享併發池。此外,一個類似棧的池可以使用一個允許增加和減少操作的計數器來實現,然後利用以上技術中的一種來並行化.
最後,在前文中說討論的消除技術(elimination technique)可以使用在利用combining tree,combining funnel,counting network,diffracting tree等技術實現的併發池中:假如在樹中插入和刪除操作相遇,那麼刪除操作可以獲取插入操作插入的值,之後兩個操作就不用繼續遍歷這個結構樹。這項技術在高負載下能表現出良好的高效能.
上述的這些實現的缺點是,它們在低複雜的情況下效能糟糕。而且,在做負載分佈工作的時候,這些實現並不允許我們如同那些專門為負載分佈設計的池一樣,得到具體的位置資訊。
負載均衡演算法涉及到一個任務池集合,每個任務池中有一些工作單元,其中每一個任務池都被本地化到一個處理器上。當執行緒建立了工作項之後就把它們置於本地化的任務池中,依靠負載均衡演算法確保任務池中的工作數量都是均衡的。這樣就避免了其他處理器仍然忙碌於自己工作的時候,有些處理器卻處於空閒狀態。大體上有兩種解決這類問題的演算法:工作共享(work sharing )和工作竊取(work stealing )。在工作共享方案中,每一個處理器嘗試不斷地將自身任務池中的工作卸下,放到其他任務池中。在工作竊取模式下,一個執行緒已經完成了自己的工作後就會去竊取其他任務池中的工作去執行。兩種方案通過隨機選擇任務池來平衡工作或竊取工作。
傳統工作竊取技術是Arara等人提出的,它基於一個無鎖結構的雙向佇列。該雙向佇列只允許一個執行緒(該任務池的本地執行緒)在佇列的一端進行操作,在另一端只允許彈出操作,並且允許在這一端的併發彈出操作線上程相互干擾的情況下中止。在這樣的約束下,雙向佇列適合於任務竊取,並且這種約束允許一種簡單的實現方式,即本地執行緒用低開銷的load and store操作來進行插入和刪除,只有當它和其他非本地刪除執行緒競爭佇列裡的最後一個任務的時候,才會依賴昂貴的CAS操作。
在一些案例中已經清晰表明一次竊取多個任務的效果是令人滿意的。一個非阻塞的多工竊取演算法由Hendler與Shavit在PODC(2002)上提出。在一些案例中同樣表明通過使用 同類工作項資訊來決定竊取那些工作是可取的。另外在SPAA(2000)上,Acar等人提出了一種位置引導(locality-guided)的竊取演算法.