• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            loop_in_codes

            低調做技術__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

            并行編程中的內存回收Hazard Pointer

            接上篇使用RCU技術實現讀寫線程無鎖,在沒有GC機制的語言中,要實現Lock free的算法,就免不了要自己處理內存回收的問題。

            Hazard Pointer是另一種處理這個問題的算法,而且相比起來不但簡單,功能也很強大。鎖無關的數據結構與Hazard指針中講得很好,Wikipedia Hazard pointer也描述得比較清楚,所以我這里就不講那么細了。

            一個簡單的實現可以參考我的github haz_ptr.c

            原理

            基本原理無非也是讀線程對指針進行標識,指針(指向的內存)要釋放時都會緩存起來延遲到確認沒有讀線程了才對其真正釋放。

            <Lock-Free Data Structures with Hazard Pointers>中的描述:

            Each reader thread owns a single-writer/multi-reader shared pointer called “hazard pointer.” When a reader thread assigns the address of a map to its hazard pointer, it is basically announcing to other threads (writers), “I am reading this map. You can replace it if you want, but don’t change its contents and certainly keep your deleteing hands off it.”

            關鍵的結構包括:Hazard pointerThread Free list

            Hazard pointer:一個讀線程要使用一個指針時,就會創建一個Hazard pointer包裝這個指針。一個Hazard pointer會被一個線程寫,多個線程讀。

            struct HazardPointer {
                    void *real_ptr; // 包裝的指針
                    ... // 不同的實現有不同的成員
                };
            
                void func() {
                    HazardPointer *hp = accquire(_real_ptr);
                    ... // use _real_ptr
                    release(hp);
                }

            Thread Free List:每個線程都有一個這樣的列表,保存著將要釋放的指針列表,這個列表僅對應的線程讀寫

            void defer_free(void *ptr) {
                    _free_list.push_back(ptr);
                }

            當某個線程要嘗試釋放Free List中的指針時,例如指針ptr,就檢查所有其他線程使用的Hazard pointer,檢查是否存在包裝了ptr的Hazard pointer,如果沒有則說明沒有讀線程正在使用ptr,可以安全釋放ptr

            void gc() {
                    for(ptr in _free_list) {
                        conflict = false
                        for (hp in _all_hazard_pointers) {
                            if (hp->_real_ptr == ptr) {
                                confilict = true
                                break
                            }
                        }
                        if (!conflict)
                            delete ptr
                    }
                }

            以上,其實就是Hazard Pointer的主要內容。

            Hazard Pointer的管理

            上面的代碼中沒有提到_all_hazard_pointersaccquire的具體實現,這就是Hazard Pointer的管理問題。

            《鎖無關的數據結構與Hazard指針》文中創建了一個Lock free的鏈表來表示這個全局的Hazard Pointer List。每個Hazard Pointer有一個成員標識其是否可用。這個List中也就保存了已經被使用的Hazard Pointer集合和未被使用的Hazard Pointer集合,當所有Hazard Pointer都被使用時,就會新分配一個加進這個List。當讀線程不使用指針時,需要歸還Hazard Pointer,直接設置可用成員標識即可。要gc()時,就直接遍歷這個List。

            要實現一個Lock free的鏈表,并且僅需要實現頭插入,還是非常簡單的。本身Hazard Pointer標識某個指針時,都是用了后立即標識,所以這個實現直接支持了動態線程,支持線程的掛起等。

            nbds項目中也有一個Hazard Pointer的實現,相對要弱一點。它為每個線程都設置了自己的Hazard Pointer池,寫線程要釋放指針時,就訪問所有其他線程的Hazard Pointer池。

            typedef struct haz_local {
                    // Free List
                    pending_t *pending; // to be freed
                    int pending_size;
                    int pending_count;
            
                    // Hazard Pointer 池,動態和靜態兩種
                    haz_t static_haz[STATIC_HAZ_PER_THREAD];
            
                    haz_t **dynamic;
                    int dynamic_size;
                    int dynamic_count;
            
                } __attribute__ ((aligned(CACHE_LINE_SIZE))) haz_local_t;
            
                static haz_local_t haz_local_[MAX_NUM_THREADS] = {};

            每個線程當然就涉及到haz_local_索引(ID)的分配,就像使用RCU技術實現讀寫線程無鎖中的一樣。這個實現為了支持線程動態創建,就需要一套線程ID的重用機制,相對復雜多了。

            附錄

            最后,附上一些并行編程中的一些概念。

            Lock Free & Wait Free

            常常看到Lock FreeWait Free的概念,這些概念用于衡量一個系統或者說一段代碼的并行級別,并行級別可參考并行編程——并發級別。總之Wait Free是一個比Lock Free更牛逼的級別。

            我自己的理解,例如《鎖無關的數據結構與Hazard指針》中實現的Hazard Pointer鏈表就可以說是Lock Free的,注意它在插入新元素到鏈表頭時,因為使用CAS,總免不了一個busy loop,有這個特征的情況下就算是Lock Free,雖然沒鎖,但某個線程的執行情況也受其他線程的影響。

            相對而言,Wait Free則是每個線程的執行都是獨立的,例如《鎖無關的數據結構與Hazard指針》中的Scan函數。“每個線程的執行時間都不依賴于其它任何線程的行為”

            鎖無關(Lock-Free)意味著系統中總存在某個線程能夠得以繼續執行;而等待無關(Wait-Free)則是一個更強的條件,它意味著所有線程都能往下進行。

            ABA問題

            在實現Lock Free算法的過程中,總是要使用CAS原語的,而CAS就會帶來ABA問題。

            在進行CAS操作的時候,因為在更改V之前,CAS主要詢問“V的值是否仍然為A”,所以在第一次讀取V之后以及對V執行CAS操作之前,如果將值從A改為B,然后再改回A,會使基于CAS的算法混亂。在這種情況下,CAS操作會成功。這類問題稱為ABA問題。

            Wiki Hazard Pointer提到了一個ABA問題的好例子:在一個Lock free的棧實現中,現在要出棧,棧里的元素是[A, B, C]head指向棧頂,那么就有compare_and_swap(target=&head, newvalue=B, expected=A)。但是在這個操作中,其他線程把A B都出棧,且刪除了B,又把A壓入棧中,即[A, C]。那么前一個線程的compare_and_swap能夠成功,此時head指向了一個已經被刪除的B。stackoverflow上也有個例子 Real-world examples for ABA in multithreading

            對于CAS產生的這個ABA問題,通常的解決方案是采用CAS的一個變種DCAS。DCAS,是對于每一個V增加一個引用的表示修改次數的標記符。對于每個V,如果引用修改了一次,這個計數器就加1。然后再這個變量需要update的時候,就同時檢查變量的值和計數器的值。

            但也早有人提出DCAS也不是ABA problem 的銀彈

            posted on 2015-05-03 20:46 Kevin Lynx 閱讀(20014) 評論(0)  編輯 收藏 引用 所屬分類: c/c++

            久久噜噜电影你懂的| 国产亚洲精久久久久久无码AV| 一本色道久久88加勒比—综合| 性欧美大战久久久久久久久| 亚洲另类欧美综合久久图片区| 久久国产成人午夜aⅴ影院| 国产99久久九九精品无码| 久久精品九九亚洲精品天堂| 亚洲乱亚洲乱淫久久| 国产精品亚洲美女久久久| 久久香蕉一级毛片| 精品久久久久久久久久中文字幕| 中文字幕亚洲综合久久| 观看 国产综合久久久久鬼色 欧美 亚洲 一区二区 | 欧美精品乱码99久久蜜桃| 久久久久亚洲AV成人网人人网站| 久久久久人妻精品一区三寸蜜桃 | 国内精品久久久久久久涩爱| 久久96国产精品久久久| 久久91精品综合国产首页| 久久影院亚洲一区| 99精品久久久久久久婷婷| 国产精品久久久久…| 狠狠人妻久久久久久综合蜜桃 | 国产精品99久久久精品无码| 久久久久亚洲AV无码永不| 国产精品久久久亚洲| 久久国产香蕉一区精品| 伊人久久大香线蕉av一区| 日韩一区二区久久久久久| 亚洲精品美女久久久久99小说| 久久精品国产亚洲av日韩| 国产免费久久久久久无码| 久久久久久久女国产乱让韩| 久久国产成人精品麻豆| 国内精品久久久久影院亚洲| 久久综合丝袜日本网| 亚洲AV无码久久精品色欲| 国产精品成人久久久久久久| 午夜欧美精品久久久久久久| 久久国产成人午夜aⅴ影院|