日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

无锁数据结构一

發布時間:2024/1/17 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 无锁数据结构一 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

lock-free

本文對lock-free進行介紹,主要介紹原子性操作,下一篇介紹內存訪問控制

什么是lock-free

無鎖數據結構

無鎖數據結構的實現主要基于兩個方面:原子性操作和內存訪問控制方法。下面先記錄原子性操作相關知識。

原子性操作

原子性操作可以簡單地分為讀寫(read and write)、原子性交換操作(read-modify-write,RMW)兩部分。原子操作可認為是一個不可分的操作;要么發生,要么沒發生,我們看不到任何執行的中間過程,不存在部分結果(partial effects),就像事務。

在現代處理器中,簡單的數據類型的對齊讀寫操作一般是原子的,而RMW更進一步的,允許進行更復雜的原子事務性操作。

下面以x86架構為例介紹其如何實現原子性操作,其通過三種機制保證原子性:(詳情可參考1、2)

  • 一些基本的內存讀寫操作已由硬件保證了其原子性。如單字節讀寫,奔騰6和最新的處理器能自動保證單處理器對同一個緩存行里進行16/32/64位的操作是原子的。
  • 通過對總線加鎖來保證。所謂總線鎖就是使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那么該處理器可以獨占使用共享內存。
  • 通過對緩存加鎖來保證。在同一時刻我們只需保證對某個內存地址的操作是原子性即可,但總線鎖定把CPU和內存之間通信鎖住了,這使得鎖定期間,其他處理器不能操作其他內存地址的數據,所以總線鎖定的開銷比較大,最近的處理器在某些場合下使用緩存鎖定代替總線鎖定來進行優化。
  • 在不同平臺RMW操作有不同實現,如:

    • _InterlockedIncrement?_InterlockedCompareExchange(Win32);
    • OSAtomicAdd32(iOS);
    • std::atomic<>::fetch_add``std::atomic<>::compare_exchange_strong?(c++11) .

    在構建無鎖數據結構時需要用到RMW操作,其包括:compare-and-swap (CAS)、fetch-and-add (FAA)、test-and-set (TAS) 等等。其中最基本的是CAS,其他操作可通過CAS實現。

    CAS

    CAS偽代碼如下:

    bool CAS( int * pAddr, int nExpected, int nNew ) atomically {if ( *pAddr == nExpected ) {*pAddr = nNew ;return true ;}elsereturn false ; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    但在實際中往往會想要知道,失敗后,pAddr的當前值是多少,故可修改如下。

    int CAS( int * pAddr, int nExpected, int nNew ) atomically {if ( *pAddr == nExpected ) {*pAddr = nNew ;return nExpected ;}elsereturn *pAddr }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    c++11中CAS為?
    std::atomic::compare_exchange_weak,?
    std::atomic::compare_exchange_strong.?
    其如同用std::memcmp?原子地比較?*this?的對象表示和?expected?的對象表示,而若它們逐位相等,則以?desired?替換前者(進行讀修改寫操作)。否則,將?*this?中的實際值加載進?expected?(進行加載操作)。如同用?std::memcpy?進行復制。詳情可查看cppreference

    ABA問題

    ABA問題是所以基于CAS基本類型的無鎖容器的一個巨大災難.CAS算法實現的一個重要前提是需要取出內存中某時刻的數據,而在下一時刻比較并替換,那么就存在了一個時間差,這個時間差會內數據有可能變化。

    考慮一個棧數據順序為 ABC,線程1pop,執行到取出A值,但還沒CAS,此時要替換成的數據為A->next即B線程2pop了A、B,然后push?A?
    ,順序變為AC,然后線程1繼續執行CAS,比較時棧頂仍為A比較成功,替換成B(已不在棧中),棧數據出錯。

    其解決方法有:標簽指針(Tagged pointers)、險象指針(Hazard pointer)等。在其他文章介紹。

    對于實現了load-linked、store-conditional (LL/SC) 這樣的操作對的處理器,其不會發生ABA問題。

    其偽代碼如下:

    word LL( word * pAddr ) { return *pAddr ; }bool SC( word * pAddr, word New ) {if ( data in pAddr has not been changed since the LL call) {*pAddr = New ; return true ;}else return false ; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    LL/SC對以括號運算符的形式運行,Load-linked(LL) 運算僅僅返回 pAddr 地址的當前變量值。如果 pAddr 中的數據在讀取之后沒有變化,那么 Store-conditional(SC) )操作會將LL讀取 pAddr 地址的數據存儲起來。這種變化之下,任何 pAddr 引用的緩存行修改都是明確無誤的。為了實現 LL/SC 對,程序員不得不更改緩存結構。簡而言之,每個緩存行必須含有額外的比特狀態值(status bit)。一旦LL執行讀運算,就會關聯此比特值。任何的緩存行一旦有寫入,此比特值就會被重置;在存儲之前,SC操作會檢查此比特值是否針對特定的緩存行。如果比特值為1,意味著緩存行沒有任何改變,pAddr 地址中的值會變更為新值,SC操作成功。否則本操作就會失敗,pAddr 地址中的值不會變更為新值。

    CAS通過LL/SC對得以實現,具體如下:

    bool CAS( word * pAddr, word nExpected, word nNew ) {if ( LL( pAddr ) == nExpected ) return SC( pAddr, nNew ) ; return false ; }
    • 1
    • 2
    • 3
    • 4
    • 5

    但LL/SC由于偽共享(False sharing)也存在問題,簡單來說即是多個共享變量使用同一緩存行,只要有一個變量改變,即認為緩存行數據無效(SC失敗),詳情可參考4。

    NOTE

    C++11的原子標準并不保證其在所以平臺的實現都是lock-free的(std::atomic_flag 除外),可通過std::atomic<>::is_lock_free確認

    All atomic types except for std::atomic_flag may be implemented using mutexes or other locking operations, rather than using the lock-free atomic CPU instructions. Atomic types are also allowed to be sometimes lock-free, e.g. if only aligned memory accesses are naturally atomic on a given architecture, misaligned objects of the same type have to use locks.

    參考資料

  • 多線程程序中操作的原子性
  • 原子操作的實現原理
  • An Introduction to Lock-Free Programming
  • 無鎖數據結構(基礎篇):原子性、原子性原語
  • 總結

    以上是生活随笔為你收集整理的无锁数据结构一的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。