DISRUPT 快的原因
阿新 • • 發佈:2018-11-05
DISRUPT 快的原因
標籤(空格分隔): 佇列 生產者 消費者
---
在我們開發過程中經常使用的就是arrayBlockingqueue,再看《億級流量架構》的時候發現還有一個更好用的東西 ===》Disruptor(無鎖併發)
我看了一下官網的介紹,還有併發程式設計網中的翻文 總結了以下幾點為什麼快的原因
##Disruptor根本就不用鎖
disruptor使用的是一個ringBuffer環形的陣列結構,而且有一個容易預測的訪問模式。(譯者注:陣列內元素的記憶體地址的連續性儲存的)。這是對CPU快取友好的—也就是說,在硬體級別,陣列中的元素是會被預載入的,因此在ringbuffer當中,cpu無需時不時去主存載入陣列中的下一個元素。(校對注:因為只要一個元素被載入到快取行,其他相鄰的幾個元素也會被載入進同一個快取行)它不像佇列一樣使用tail和head指定位置,它只需一個指向下一個位置點的序號,並且獲取資料的時候也是無鎖的
其次,你可以為陣列預先分配記憶體,使得陣列物件一直存在(除非程式終止)。這就意味著不需要花大量的時間用於垃圾回收。此外,不像連結串列那樣,需要為每一個新增到其上面的物件創造節點物件—對應的,當刪除節點時,需要執行相應的記憶體清理操作。
![此處輸入圖片的描述][1]
取而代之的是,在需要確保操作是執行緒安全的(特別是,在多生產者的環境下,更新下一個可用的序列號)地方,我們使用CAS(Compare And Swap/Set)操作。這是一個CPU級別的指令,在我的意識中,它的工作方式有點像樂觀鎖——CPU去更新一個值,但如果想改的值不再是原來的值,操作就失敗,因為很明顯,有其它操作先改變了這個值。
![此處輸入圖片的描述][2]
**注意,這可以是CPU的兩個不同的核心,但不會是兩個獨立的CPU。**
CAS操作比鎖消耗資源少的多,因為它們不牽涉作業系統,它們直接在CPU上操作。但它們並非沒有代價——在上面的試驗中,單執行緒無鎖耗時300ms,單執行緒有鎖耗時10000ms,單執行緒使用CAS耗時5700ms。所以它比使用鎖耗時少,但比不需要考慮競爭的單執行緒耗時多。
![此處輸入圖片的描述][3]
##消費者無鎖,類似訂閱邏輯不用輪訓是否有新資料
![此處輸入圖片的描述][4]
消費者可以呼叫ConsumerBarrier物件的waitFor()方法,傳遞它所需要的下一個序號.
final long availableSeq = consumerBarrier.waitFor(nextSequence);
ConsumerBarrier返回RingBuffer的最大可訪問序號——在上面的例子中是12。ConsumerBarrier有一個WaitStrategy方法來決定它如何等待這個序號
consumerBarrier監聽了生產者那邊的一個Strategy,一旦發現生產者有新資料進來了就通知這個waitStrategy 當然waitStrategy 這個策略有三種。
![此處輸入圖片的描述][5]
有助於平緩延遲的峰值了——以前需要逐個節點地詢問“我可以拿下一個資料嗎?現在可以了麼?現在呢?”,消費者(Consumer)現在只需要簡單的說“當你拿到的數字比我這個要大的時候請告訴我”,函式返回值會告訴它有多少個新的節點可以讀取資料了。因為這些新的節點的確已經寫入了資料(Ring Buffer本身的序號已經更新),而且消費者對這些節點的唯一操作是讀而不是寫,因此訪問不用加鎖。這太好了,不僅程式碼實現起來可以更加安全和簡單,而且不用加鎖使得速度更快。
另一個好處是——你可以用多個消費者(Consumer)去讀同一個RingBuffer ,不需要加鎖,也不需要用另外的佇列來協調不同的執行緒(消費者)。這樣你可以在Disruptor的協調下實現真正的併發資料處理
##生產者無鎖
![此處輸入圖片的描述][6]
生產者也有一個ProducerBarrier,每個生產者都是通過這個是拿下一個可用的位置節點,當然因為是一個環形,如果有消費者消費的慢,比如說消費者消費到3,生產者就不能拿到3之後的位置點進行存入。只會在那裡等待。如果最後的消費者都消費到了11位置,那麼3到11位置之間的空檔,生產就可以拿去存了。這一切位置是否有空餘,都是claimStrategy 通知生產者的。
另外要說的一點就生產者是分兩階段提交的,假如3-11的位置有空餘,ProducerBarrier允許多個生產者一起生成然後提交,當然生產者有快,有慢,在最後提交的時候,要按順序提交,好比說生產者1已經把7位置點的資料都準備好的,生產者2生成6位置資料還沒有提交。生產者1就需要等待生產者2提交了再提交。不過這樣也算是併發的生產吧。比一個個強的多,是吧
部分是在併發程式設計網摘抄下來的,如果越權,聯絡我刪除
[6om/wp-content/uploads/2013/01/RingBufferReplay.png
[1]: http://ifeve.com/wp-content/uploads/2013/01/RingBufferInitial.png
[2]: http://ifeve.com/wp-content/uploads/2013/01/ConcurrencyCAS.png
[3]: http://ifeve.com/wp-content/uploads/2013/01/RingBufferReplay.png
[4]: http://ifeve.com/wp-content/uploads/2013/02/Image21.png
[5]: http://ifeve.com/wp-content/uploads/2013/01/ProducerBatching.png
[6]: http://ifeve.com/wp-content/uploads/2013/01/ProducerNextEntry.png
標籤(空格分隔): 佇列 生產者 消費者
---
在我們開發過程中經常使用的就是arrayBlockingqueue,再看《億級流量架構》的時候發現還有一個更好用的東西 ===》Disruptor(無鎖併發)
我看了一下官網的介紹,還有併發程式設計網中的翻文 總結了以下幾點為什麼快的原因
##Disruptor根本就不用鎖
disruptor使用的是一個ringBuffer環形的陣列結構,而且有一個容易預測的訪問模式。(譯者注:陣列內元素的記憶體地址的連續性儲存的)。這是對CPU快取友好的—也就是說,在硬體級別,陣列中的元素是會被預載入的,因此在ringbuffer當中,cpu無需時不時去主存載入陣列中的下一個元素。(校對注:因為只要一個元素被載入到快取行,其他相鄰的幾個元素也會被載入進同一個快取行)它不像佇列一樣使用tail和head指定位置,它只需一個指向下一個位置點的序號,並且獲取資料的時候也是無鎖的
其次,你可以為陣列預先分配記憶體,使得陣列物件一直存在(除非程式終止)。這就意味著不需要花大量的時間用於垃圾回收。此外,不像連結串列那樣,需要為每一個新增到其上面的物件創造節點物件—對應的,當刪除節點時,需要執行相應的記憶體清理操作。
![此處輸入圖片的描述][1]
取而代之的是,在需要確保操作是執行緒安全的(特別是,在多生產者的環境下,更新下一個可用的序列號)地方,我們使用CAS(Compare And Swap/Set)操作。這是一個CPU級別的指令,在我的意識中,它的工作方式有點像樂觀鎖——CPU去更新一個值,但如果想改的值不再是原來的值,操作就失敗,因為很明顯,有其它操作先改變了這個值。
![此處輸入圖片的描述][2]
**注意,這可以是CPU的兩個不同的核心,但不會是兩個獨立的CPU。**
CAS操作比鎖消耗資源少的多,因為它們不牽涉作業系統,它們直接在CPU上操作。但它們並非沒有代價——在上面的試驗中,單執行緒無鎖耗時300ms,單執行緒有鎖耗時10000ms,單執行緒使用CAS耗時5700ms。所以它比使用鎖耗時少,但比不需要考慮競爭的單執行緒耗時多。
![此處輸入圖片的描述][3]
##消費者無鎖,類似訂閱邏輯不用輪訓是否有新資料
![此處輸入圖片的描述][4]
消費者可以呼叫ConsumerBarrier物件的waitFor()方法,傳遞它所需要的下一個序號.
final long availableSeq = consumerBarrier.waitFor(nextSequence);
ConsumerBarrier返回RingBuffer的最大可訪問序號——在上面的例子中是12。ConsumerBarrier有一個WaitStrategy方法來決定它如何等待這個序號
consumerBarrier監聽了生產者那邊的一個Strategy,一旦發現生產者有新資料進來了就通知這個waitStrategy 當然waitStrategy 這個策略有三種。
![此處輸入圖片的描述][5]
有助於平緩延遲的峰值了——以前需要逐個節點地詢問“我可以拿下一個資料嗎?現在可以了麼?現在呢?”,消費者(Consumer)現在只需要簡單的說“當你拿到的數字比我這個要大的時候請告訴我”,函式返回值會告訴它有多少個新的節點可以讀取資料了。因為這些新的節點的確已經寫入了資料(Ring Buffer本身的序號已經更新),而且消費者對這些節點的唯一操作是讀而不是寫,因此訪問不用加鎖。這太好了,不僅程式碼實現起來可以更加安全和簡單,而且不用加鎖使得速度更快。
另一個好處是——你可以用多個消費者(Consumer)去讀同一個RingBuffer ,不需要加鎖,也不需要用另外的佇列來協調不同的執行緒(消費者)。這樣你可以在Disruptor的協調下實現真正的併發資料處理
##生產者無鎖
![此處輸入圖片的描述][6]
生產者也有一個ProducerBarrier,每個生產者都是通過這個是拿下一個可用的位置節點,當然因為是一個環形,如果有消費者消費的慢,比如說消費者消費到3,生產者就不能拿到3之後的位置點進行存入。只會在那裡等待。如果最後的消費者都消費到了11位置,那麼3到11位置之間的空檔,生產就可以拿去存了。這一切位置是否有空餘,都是claimStrategy 通知生產者的。
另外要說的一點就生產者是分兩階段提交的,假如3-11的位置有空餘,ProducerBarrier允許多個生產者一起生成然後提交,當然生產者有快,有慢,在最後提交的時候,要按順序提交,好比說生產者1已經把7位置點的資料都準備好的,生產者2生成6位置資料還沒有提交。生產者1就需要等待生產者2提交了再提交。不過這樣也算是併發的生產吧。比一個個強的多,是吧
部分是在併發程式設計網摘抄下來的,如果越權,聯絡我刪除
[6om/wp-content/uploads/2013/01/RingBufferReplay.png
[1]: http://ifeve.com/wp-content/uploads/2013/01/RingBufferInitial.png
[2]: http://ifeve.com/wp-content/uploads/2013/01/ConcurrencyCAS.png
[3]: http://ifeve.com/wp-content/uploads/2013/01/RingBufferReplay.png
[4]: http://ifeve.com/wp-content/uploads/2013/02/Image21.png
[5]: http://ifeve.com/wp-content/uploads/2013/01/ProducerBatching.png
[6]: http://ifeve.com/wp-content/uploads/2013/01/ProducerNextEntry.png