1. 程式人生 > 其它 >風險指標(hazard pointer)

風險指標(hazard pointer)

前言

無鎖(Lock-free)物件比傳統的基於鎖的物件提供了顯著的效能和可靠性等優點。然而,由於缺乏一個有效可移植的無鎖方法來回收這些物件中刪除掉的動態節點所佔用的記憶體,成為了在實踐中廣泛應用該方法的一個主要障礙。風險指標是一種記憶體管理的方法,允許記憶體被

回收後任意重用。該記憶體管理方法是無等待的(wait-free)。核心操作僅需要單字的記憶體讀取和寫入訪問。該方法同樣提供了ABA問題的一個無鎖解決方案。對於指標的管理,通常關注與如何回收已經刪除掉的物件所佔用的記憶體。在基於鎖的物件的情形下,當執行緒從物件中

刪除一個節點時,在它被重用或者重新分配之前,很容易保證沒有其他執行緒會在此後訪問該節點的記憶體。但在無鎖動態物件就出現了問題。為了保證無鎖的物件的順利執行,每個執行緒必須要有無限制的機會在任意時間操作該物件。

核心思想

核心思想是,將一些(典型地是1或2個)被稱為風險指標(hazard pointers)的單寫者多讀者(single-writer multi-reader)的共享指標,與每一個想要訪問無鎖物件的執行緒關聯起來。一個風險指標要麼是NULL,要麼指向一個節點,該節點之後可能會被該執行緒訪問,而不需要

驗證對該節點的這個引用是否有效。每個風險指標只能被其擁有者執行緒寫入,但是可以被別的執行緒讀取。

該方法要求無鎖演算法保證,當動態節點可能已經被從物件中刪除掉的時候,沒有任何執行緒可以訪問該動態節點,除非該執行緒的至少一個關聯的風險指標從該節點還可以保證能夠從物件的根到達開始已經連續指向該節點。該方法防止釋放任何已經在其被刪除之前已經被一個或

多個執行緒的一個或者多個風險指標指向的節點。

每當一個執行緒退休一個節點,它就將其保持在一個私有列表中。在累積了一定數量R個退休節點之後,該執行緒就掃描其它執行緒的風險指標,尋找與累積的節點地址的匹配。如果退休節點沒有與任何風險指標匹配,那麼釋放該節點就是安全的。否則,該執行緒繼續保持該節點,直

到它下次掃描這些風險指標。

解決方案

建立一個全域性陣列,陣列容量為執行緒數目,每個執行緒只能修改自己的陣列元素,而不允許修改其他的陣列元素,但可以讀別的陣列元素。

當執行緒嘗試訪問一個關鍵資料節點時,先把該節點指標賦給自己的陣列元素(即不要釋放這個節點)。

每個執行緒自己維護一個私有連結串列,當執行緒準備釋放掉某個節點時,將該節點放入到連結串列中。當連結串列內的數目達到一個設定的數目後,遍歷該連結串列用於釋放連結串列內所有節點。

當釋放節點時,需要檢查全域性陣列,確定沒有任何一個執行緒的陣列元素與當前指標相同時,就釋放該節點。否則仍然滯留在自己的連結串列中。

主要用於比對自己的私有連結串列儲存的節點是否存在於全域性陣列中,如果出現在全域性陣列中,表示正有執行緒訪問私有連結串列中的節點,則不能刪除。

Hazard Pointer主要應用在實現無鎖佇列上。

佇列上的元素任何時候,只可能被其中一個執行緒成功地從佇列上取下來,因此每個執行緒的連結串列中的元素肯定是唯一的。

相關術語

節點(Node):我們使用術語節點(node)來描述一個記憶體位置範圍,這個記憶體位置範圍在某些時候可以被看作是一個邏輯實體,要麼通過其在使用了風險指標(hazard pointers)的物件中的實際使用,要麼通過參與執行緒的視角。因此,可能會有多個節點物理上重疊,但仍

然、被看作是不同的邏輯實體。

在任一時刻t,每個節點n處在下列狀態之一:

1.已分配(Allocated): n已經被一個參與執行緒分配,但尚未插入一個相關聯的物件。

2.可到達(Reachable):n是可以通過從根部開始跟蹤一個相關聯的物件的有效指標是可達的。

3.已刪除(Removed):n不再可到達,但仍可能在刪除執行緒(removing thread)中正被使用。

4.已退休(Retired): n已經被刪除並被刪除執行緒用完,但尚未被釋放。

5.空閒(Free):n的記憶體可以被分配使用。

6.不可用(Unavailable):n的部分或全部記憶體被一個不相干的物件在使用。

7.未定義(Undefined):n的記憶體訪問目前沒有被當作一個節點看待。

擁有(Own):一個執行緒j在時刻t擁有一個節點,當且僅當在時刻t節點n對於執行緒j 屬於allocated, removed, 或者retired狀態。每個節點都可以有最多一個擁有者。被分配的(allocated)節點的擁有者(owner)是分配它的執行緒(例如,通過呼叫malloc)。被刪除的(removed)節點的擁有者(owner)是執行將它從物件中刪除步驟的執行緒(也即,從將其狀態從可到達(reachable)改為已刪除(removed))。已退休(retired)節點的擁有者(owner)與刪除它的執行緒是同一個執行緒。

安全的(Safe):一個節點n對於執行緒j在時間t是安全的,當且僅當在時間t,要麼n是可達的,要麼j擁有n。

可能是不安全的(Possibly unsafe):一個節點從執行緒j的視角中在時間t可能是不安全的(possibly unsafe),如果不可能僅僅通過檢查j的私有變數以及該演算法的語義,就可以確定對於j在時間t該節點絕對肯定是安全的。

訪問風險(Access hazard):線上程j的演算法的一個步驟是一個訪問風險(Access hazard),當且僅當它可能會導致訪問到對於執行緒j在其執行的時間內可能是不安全的(possibly unsafe)節點。

ABA風險(ABA hazard):執行緒j的演算法中的一個步驟s是一個ABA風險(ABA hazard),當且僅當執行緒j在執行s時在一個可能不安全的(possiblyunsafe)動態節點上包括了容易發生ABA問題的比較(ABA-prone comparison)操作,這樣,1)節點的地址,或它的一個算術變體,是容易發生ABA問題的比較(ABA-prone comparison)操作的一個預期值,或2)包含在動態節點中的記憶體位置是容易發生ABA問題的比較(ABA-prone comparison)操作的目標。

有訪問風險的引用(Access-hazardous reference):執行緒j在時刻t包含對節點n的一個有訪問風險的引用(accesshazardous reference),當且僅當在時刻t,j的一個或者多個私有變數持有n的地址,或者其算數變體,並且j可以保證(除非它崩潰了)能夠到達訪問風險(access hazard)s,在那裡有風險地使用n的地址,也即,在n對於j可能是不安全的(possibly unsafe)時候訪問n。【譯註:真TNND的繞啊,基本意思就是,存在一個引用,肯定能到達,但是一旦訪問就有風險!】

有ABA風險的引用(ABA-hazardous reference):執行緒j在時刻t包含對節點n的一個有ABA風險的引用(ABA-hazardousreference),當且僅當在時刻t,j的一個或者多個私有變數持有n的地址,或者其算數變體,並且j可以保證(除非它崩潰了)能夠到達ABA風險(ABA hazard)s,在那裡有風險地使用n的地址。

有風險的引用(Hazardous reference):一個引用是有風險的,如果它有訪問風險(access-hazardous),和/或者有ABA風險(ABA-hazardous)。

非正式地,有風險的引用是一個地址,不經進一步的安全性驗證,之後會以存在風險的方式被使用,即,在容易發生ABA問題的比較(ABA-prone comparison)操作中訪問可能不安全的儲存器,和/或作為目標地址,或者預期值。