《c++併發編程實戰解析》 無鎖數據結構 doubly-buffered-data

多線程環境設計數據結構相比單線程,需要額外注意的是利用多線程提升併發度同時保持數據結構不變性,即滿足如下兩個原則:

1、正確性,保證多線程併發訪問沒有 data race

2、性能,保護最小的數據,提供最大的性能

《c++併發編程實戰》提供了一種【無鎖數據結構】,注意這裏無鎖的含義不是真正無鎖,而是利用數據結構特性保證運行時併發搶鎖的線程數量最小,達到一種常態下訪問數據結構不被鎖阻塞的狀態

使用場景:

1、讀遠多於寫

2、數據小,一般最大爲幾K字節的元數據

3、數據可以應用到一致性狀態機,即byte級別判斷爲相等的兩份數據,執行同種操作後仍保持一致

和傳統讀寫鎖的區別和優勢:single unix的讀寫鎖要求有線程在獲取寫鎖阻塞時,後續讀鎖阻塞,從而防止寫鎖被餓死,這樣造成了一個後果,若有寫請求到來,則寫操作完成前,無法處理新的讀請求,從而造成系統【顛簸】,在有寫請求的時候對外表現爲性能下降,讀延遲增加。DoublyBufferData將數據保存兩份,分成前端和後端,讀請求讀前端,寫請求到來寫後端,用c++11  memory_order語義保證寫請求完成後新的讀請求可以立刻讀取最新的結果同時無data race,是一種典型的【空間換時間】模式

數據結構圖示:

原理描述:

1、DoublyBufferedData中包含foreground和background兩份數據,index值爲0或1,指示讀請求應該訪問的數據,如index爲0表示此時應該讀取data0,data0是隻讀的,因此多線程訪問不需要併發保護,這裏是無鎖的

2、當有線程請求修改,首先根據 !index 獲取background並做修改,background沒有讀者,因此這裏可以放心修改,修改完成後,data0是舊數據,data1是新數據,使用memory_order_release語義修改index = !index,這樣後續使用memory_order_acquire語義訪問index的線程可以看到修改(inter-thread的sychronization-with關係)

3、對於此前正在訪問data0的線程,通過遍歷wrappers可以得到這些線程各自對應的wrapper,進而等待所有這些線程訪問結束

4、到這裏,所有之前讀data0的請求都結束了,所有後續的讀請求都請求到data1,此時可以放心的修改data0,從而完成一次修改操作

代碼:

1、DoublyBufferedDataWrapperBase:

提供線程私有存儲的ABC類,T是數據類型,TLS用於線程私有存儲,可用於保存上下文,沒有特殊要求不需要使用TLS

template <typename T, typename TLS>
class DoublyBufferedDataWrapperBase {
public:
    TLS& user_tls() { return _user_tls; }
protected:
    TLS _user_tls;
};

2、Wrapper:

每個線程使用一個wrapper對數據進行訪問,包含一個pthread_mutex_t,因爲讀沒有併發,因此這個鎖是沒有競爭的,近似於無鎖訪問

template <typename T, typename TLS>
class DoublyBufferedData<T, TLS>::Wrapper
    : public DoublyBufferedDataWrapperBase<T, TLS> {
friend class DoublyBufferedData;
public:
    explicit Wrapper() : _control(NULL) {
        pthread_mutex_init(&_mutex, NULL);
    }
    ~Wrapper() {
        if (_control != NULL) {
            _control->RemoveWrapper(this);
        }
        pthread_mutex_destroy(&_mutex);
    }
    inline void BeginRead() {
        pthread_mutex_lock(&_mutex);
    }

    inline void EndRead() {
        pthread_mutex_unlock(&_mutex);
    }

    inline void WaitReadDone() {
        LOCK_GUARD(mutex);
    }

private:
    DoublyBufferedData* _control;
    pthread_mutex_t _mutex;
};

3、WrapperTLSGroup

全局管理器,每個希望使用DoublyBufferedData的數據向這個類申請一個全局唯一id,訪問此數據的thread根據這個id申請自己的wrapper,每個線程內的wrapper用block組織,一個block大小4k字節,超過4k的數據獨佔block,否則一個block內分配多個wrapper

template <typename T, typename TLS>
class DoublyBufferedData<T, TLS>::WrapperTLSGroup {
public:
    const static size_t RAW_BLOCK_SIZE = 4096;
    const static size_t ELEMENTS_PER_BLOCK = (RAW_BLOCK_SIZE + sizeof(T) - 1) / sizeof(T);

    struct __declspec(align(64)) ThreadBlock {
        inline DoublyBufferedData::Wrapper* at(size_t offset) {
            return _data + offset;
        };

    private:
        DoublyBufferedData::Wrapper _data[ELEMENTS_PER_BLOCK];
    };

    inline static WrapperTLSId key_create() {
        LOCK_GUARD(_s_mutex);
        WrapperTLSId id = 0;
        if (!_get_free_ids().empty()) {
            id = _get_free_ids().back();
            _get_free_ids().pop_back();
        } else {
            id = _s_id++;
        }
        return id;
    }

    inline static int key_delete(WrapperTLSId id) {
        LOCK_GUARD(_s_mutex);
        if (id < 0 || id >= _s_id) {
            errno = EINVAL;
            return -1;
        }
        _get_free_ids().push_back(id);
        return 0;
    }

    inline static DoublyBufferedData::Wrapper* get_or_create_tls_data(WrapperTLSId id) {
        if (unlikely(id < 0)) {
            CHECK(false) << "Invalid id=" << id;
            return NULL;
        }
        if (_s_tls_blocks == NULL) {
            _s_tls_blocks = new (std::nothrow) std::vector<ThreadBlock*>;
            if (BAIDU_UNLIKELY(_s_tls_blocks == NULL)) {
                LOG(FATAL) << "Fail to create vector, " << berror();
                return NULL;
            }
            thread_atexit(_destroy_tls_blocks);
        }
        const size_t block_id = (size_t)id / ELEMENTS_PER_BLOCK;
        if (block_id >= _s_tls_blocks->size()) {
            _s_tls_blocks->resize(std::max(block_id + 1, 32ul));
        }
        ThreadBlock* tb = (*_s_tls_blocks)[block_id];
        if (tb == NULL) {
            ThreadBlock* new_block = new (std::nothrow) ThreadBlock;
            if (BAIDU_UNLIKELY(new_block == NULL)) {
                return NULL;
            }
            tb = new_block;
            (*_s_tls_blocks)[block_id] = new_block;
        }
        return tb->at(id - block_id * ELEMENTS_PER_BLOCK);
    }

private:
    static void _destroy_tls_blocks() {
        if (!_s_tls_blocks) {
            return;
        }
        for (size_t i = 0; i < _s_tls_blocks->size(); ++i) {
            delete (*_s_tls_blocks)[i];
        }
        delete _s_tls_blocks;
        _s_tls_blocks = NULL;
    }

    inline static std::deque<WrapperTLSId>& _get_free_ids() {
        if (unlikely(!_s_free_ids)) {
            _s_free_ids = new (std::nothrow) std::deque<WrapperTLSId>();
            if (!_s_free_ids) {
                abort();
            }
        }
        return *_s_free_ids;
    }

private:
    static pthread_mutex_t _s_mutex;
    static WrapperTLSId _s_id;
    static std::deque<WrapperTLSId>* _s_free_ids;
    static __thread std::vector<ThreadBlock*>* _s_tls_blocks;
};

4、ScopedPtr:

提供給調用者使用的數據結構,調用者通過此類進行數據訪問,內部封裝併發邏輯

class ScopedPtr {
friend class DoublyBufferedData;
public:
    ScopedPtr() : _data(NULL), _w(NULL) {}
    ~ScopedPtr() {
        if (_w) {
            _w->EndRead();
        }
    }
    const T* get() const { return _data; }
    const T& operator*() const { return *_data; }
    const T* operator->() const { return _data; }
    TLS& tls() { return _w->user_tls(); }

private:
    ScopedPtr(const ScopedPtr&) = delete;
    ScopedPtr& operator=(const ScopedPtr&) = delete;
    const T* _data;
    Wrapper* _w;
};

5、DoublyBufferData:

數據存儲單元,內部包含T[0]和T[1]兩份數據,調用用戶函數進行數據修改

template <typename T, typename TLS = Void>
class DoublyBufferedData {
    class Wrapper;
    class WrapperTLSGroup;
    typedef int WrapperTLSId;
public:

    DoublyBufferedData();
    ~DoublyBufferedData();

    int Read(ScopedPtr* ptr);
    template <typename Fn> size_t Modify(Fn& fn);
    template <typename Fn, typename Arg1> size_t Modify(Fn& fn, const Arg1&);
    template <typename Fn, typename Arg1, typename Arg2>
    size_t Modify(Fn& fn, const Arg1&, const Arg2&);

    template <typename Fn> size_t ModifyWithForeground(Fn& fn);
    template <typename Fn, typename Arg1>
    size_t ModifyWithForeground(Fn& fn, const Arg1&);
    template <typename Fn, typename Arg1, typename Arg2>
    size_t ModifyWithForeground(Fn& fn, const Arg1&, const Arg2&);

private:
    template <typename Fn>
    struct WithFG0 {
        WithFG0(Fn& fn, T* data) : _fn(fn), _data(data) { }
        size_t operator()(T& bg) {
            return _fn(bg, (const T&)_data[&bg == _data]);
        }
    private:
        Fn& _fn;
        T* _data;
    };

    template <typename Fn, typename Arg1>
    struct WithFG1 {
        WithFG1(Fn& fn, T* data, const Arg1& arg1)
            : _fn(fn), _data(data), _arg1(arg1) {}
        size_t operator()(T& bg) {
            return _fn(bg, (const T&)_data[&bg == _data], _arg1);
        }
    private:
        Fn& _fn;
        T* _data;
        const Arg1& _arg1;
    };

    template <typename Fn, typename Arg1, typename Arg2>
    struct WithFG2 {
        WithFG2(Fn& fn, T* data, const Arg1& arg1, const Arg2& arg2)
            : _fn(fn), _data(data), _arg1(arg1), _arg2(arg2) {}
        size_t operator()(T& bg) {
            return _fn(bg, (const T&)_data[&bg == _data], _arg1, _arg2);
        }
    private:
        Fn& _fn;
        T* _data;
        const Arg1& _arg1;
        const Arg2& _arg2;
    };

    template <typename Fn, typename Arg1>
    struct Closure1 {
        Closure1(Fn& fn, const Arg1& arg1) : _fn(fn), _arg1(arg1) {}
        size_t operator()(T& bg) { return _fn(bg, _arg1); }
    private:
        Fn& _fn;
        const Arg1& _arg1;
    };

    template <typename Fn, typename Arg1, typename Arg2>
    struct Closure2 {
        Closure2(Fn& fn, const Arg1& arg1, const Arg2& arg2)
            : _fn(fn), _arg1(arg1), _arg2(arg2) {}
        size_t operator()(T& bg) { return _fn(bg, _arg1, _arg2); }
    private:
        Fn& _fn;
        const Arg1& _arg1;
        const Arg2& _arg2;
    };

    const T* UnsafeRead() const
    { return _data + _index.load(base::memory_order_acquire); }
    Wrapper* AddWrapper(Wrapper*);
    void RemoveWrapper(Wrapper*);

    T _data[2];

    base::atomic<int> _index;
    WrapperTLSId _wrapper_key;
    std::vector<Wrapper*> _wrappers;
    pthread_mutex_t _wrappers_mutex;
    pthread_mutex_t _modify_mutex;
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章