<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Disruptor高性能之道-無鎖化

          共 3153字,需瀏覽 7分鐘

           ·

          2022-03-06 05:00

          JUC中的隊列BlockingQueue是通過加鎖實現(xiàn)對生產(chǎn)者和消費者的協(xié)調(diào)。

          加鎖就意味著需要犧牲高性能,換來線程安全。

          有沒有辦法既能高性能,還能線程安全?

          Disruptor給出的答案是,“無鎖”。

          ?

          無鎖,并不是完全消除鎖,而是指沒有OS層面的鎖。

          Disruptor通過CAS(Compare And Swap)指令實現(xiàn)了無鎖化。具體的指令是cmpxchg,本文會做簡單講解。感興趣的讀者可以自行搜索資料了解詳細內(nèi)容。

          ?

          簡單解釋下CAS具體干了什么事情。

          ?

          CAS, 比較并交換,Compare And Swap。顧名思義,就是通過比較值是否發(fā)生變化,決定是否要重新賦值。如果在操作期間,值沒有被其他線程操作,那么就將期望的值賦值給它,否則發(fā)現(xiàn)期望的值與舊值不等,說明值已經(jīng)變更,則不執(zhí)行操作,返回操作失敗。

          ?

          舉個栗子

          比如說,舊值oldValue為1,期望的值expectValue為1,新值newValue為2。如果沒有其他線程修改舊值,那么

          • expectValue == oldValue
          • 將newValue寫入,當前值為2

          如果在操作過程中,oldValue被其他線程操作修改為2,那么當前線程的expectValue(1)與oldValue(2)比較就不等,寫入失敗。

          Disruptor如何進行CAS

          我們知道Disruptor核心數(shù)據(jù)結(jié)構(gòu)為RingBuffer,Disruptor為RingBuffer分配了一個Sequence對象,用于標識RingBuffer的頭和尾,這個標識不是通過指針實現(xiàn)的,而是通過序號。

          ?

          這個序號也就是Sequence。

          ?

          雖然邏輯上RingBuffer是一個環(huán)形數(shù)組,但是在內(nèi)存中是以一個普通的數(shù)組形式存在的。

          RingBuffer中通過對比序號的方式對生產(chǎn)者和消費者間的資源進行協(xié)調(diào)。

          每當生產(chǎn)者要往隊列中加入新數(shù)據(jù),生產(chǎn)者都會將當前sequence + 準備加入隊列的數(shù)據(jù)量,與消費者所在位置進行比較,以判斷是否存在足夠的空間放這些數(shù)據(jù),而不至于覆蓋掉消費者沒有消費的數(shù)據(jù)。

          用體育術(shù)語就叫“套圈”。

          如圖所示:ringBufferSize=16,生產(chǎn)者當前sequence指向20,消費者sequence指向27。

          我們簡單計算一下這個場景下,生產(chǎn)者能否繼續(xù)寫入4個元素。

          • 對于消費者而言,27 MOD 16 = 11
          • 對于生產(chǎn)者而言,20 + 4 = 24(預(yù)計寫入的最大序號),24 MOD 16 = 8
          • 生產(chǎn)者若成功寫入4個元素,則sequence指向數(shù)組的第8個位置,8 < 11, 表明生產(chǎn)者沒有覆蓋消費者的進度。
          • 生產(chǎn)者不需要等待消費者,直接生產(chǎn)數(shù)據(jù)即可。而且并不會覆蓋消費者未處理完的數(shù)據(jù)。

          實際上,Disruptor的代碼實現(xiàn)就是通過compareAndSet方法實現(xiàn)了CAS無鎖化操作。

          ????/**
          ?????*?Atomically?add?the?supplied?value.
          ?????*
          ?????*?@param?increment?The?value?to?add?to?the?sequence.
          ?????*?@return?The?value?after?the?increment.
          ?????*/
          ????public?long?addAndGet(final?long?increment)
          ????{
          ????????long?currentValue;
          ????????long?newValue;

          ????????do
          ????????{
          ????????????currentValue?=?get();
          ????????????newValue?=?currentValue?+?increment;
          ????????}
          ????????while?(!compareAndSet(currentValue,?newValue));

          ????????return?newValue;
          ????}

          我們可以看到,這里通過while循環(huán)不斷嘗試CAS操作,如果CAS操作不成功就會自旋重試,這個操作并沒有使用OS層面的鎖,因此效率要大幅高于OS層面的鎖機制(管程)。

          addAndGet調(diào)用了compareAndSet方法:

          ????/**
          ?????*?Perform?a?compare?and?set?operation?on?the?sequence.
          ?????*
          ?????*?@param?expectedValue?The?expected?current?value.
          ?????*?@param?newValue?The?value?to?update?to.
          ?????*?@return?true?if?the?operation?succeeds,?false?otherwise.
          ?????*/
          ????public?boolean?compareAndSet(final?long?expectedValue,?final?long?newValue)
          ????{
          ????????return?UNSAFE.compareAndSwapLong(this,?VALUE_OFFSET,?expectedValue,?newValue);
          ????}

          可以看到最終是調(diào)用了UNSAFE的compareAndSwapLong方法,該方法為native方法,在JVM層面調(diào)用了CAS指令。

          CAS指令

          上文我們提到,Disruptor的CAS最終調(diào)用的是CPU層面的機器指令「cmpxchg」。

          該指令的詳細描述:

          ??compxchg?[ax]?(隱式參數(shù),EAX?累加器),?
          ???????????[bx]?(源操作數(shù)地址),?
          ???????????[cx]?(目標操作數(shù)地址)

          簡單解釋下:

          • cmpxchg指令有三個操作數(shù),操作數(shù)ax不在指令里面出現(xiàn),是一個隱式的操作數(shù),準確地說它是EAX累加寄存器里面的值。
          • 操作數(shù)bx是源操作數(shù),指令會對比這個操作數(shù)和上面的累加寄存器里面的值是否相等,如果相等 CPU 會把 ZF(也就是條件碼寄存器里面零標志位的值)設(shè)置為 1,然后再把操作數(shù)cx(也就是目標操作數(shù))設(shè)置到源操作數(shù)的地址上。
          • 如果不相等的話,就把源操作數(shù)里面的值設(shè)置到累加器寄存器里面

          由于cmpxchg是cpu級別的指令,因此直接調(diào)用就可以保證cas操作的原子性。

          由于去除了OS層面的鎖,即便CAS存在比較操作與自旋操作,其本質(zhì)也是無鎖化操作,這種無鎖化機制消除了上下文切換,對于CPU極為友好,因此運行效率很快。

          事實上,在JUC包中,也提供了大量的CAS相關(guān)工作類方便我們操作,這些類一般以atomic開頭,如果去研究其實現(xiàn),我們同樣會發(fā)現(xiàn)最終是通過UNSAFE調(diào)用了底層的CAS實現(xiàn),實現(xiàn)無鎖化操作,減少上下文切換,提升代碼運行速率。

          ?

          加鎖導致的上下文切換之所以會顯著影響代碼運行速度,主要原因在于獲取鎖的過程中,CPU需要等待OS層面的鎖競爭結(jié)果,對于沒有獲取鎖的線程需要進行掛起,此時就需要進行上下文切換。

          上下文切換會把掛起線程的寄存器中的數(shù)據(jù)放到線程棧,該操作會導致加載到高速緩存中的數(shù)據(jù)也失效,進而導致程序運行速率比無鎖更慢。

          ?

          CAS就沒有什么問題么?

          當然CAS操作同樣也會存在缺點,那就是由于CAS操作本身需要進行對比,如果不相等則會一直自旋(busy-wait),這樣的操作會使得cpu的負載升高,全功率滿負荷運行。


          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色一级在线 | www.五月 | xxxxx在线视频 | 特级特黄特色大片免费看 | 伊人久久久久亚洲AV无码裤子 |