成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

剖析Disruptor:為什么會這么快?(一)鎖的缺點

開發 后端
“Disruptor究竟是什么?"。目前我正準備在回答這個問題,但首先回答"為什么它會這么快?"

artin Fowler寫了一篇非常好的文章,里面不僅提到了Disruptor,而且還解釋了Disruptor 如何應用在LMAX的架構里。里面有提及了一些目前沒有涉及的概念,但最經常問到的問題是 “Disruptor究竟是什么?"。

目前我正準備在回答這個問題,但首先回答"為什么它會這么快?"

這些問題持續出現,但是我不能沒有說它是干什么的就說它為什么會這么快,不能沒有說它為什么這樣做就說它是什么東西。

所以我陷入了一個僵局,一個如何寫博客的僵局。

要打破這個僵局,我準備以最簡單方式回答第一個問題,如果幸運的話,在以后博文里,如果需要解釋的話我會重新提回:Disruptor提供了一種線程之間信息交換的方式。

作為一個開發者,因為"線程"一詞的出現,我的警鐘已經敲響,它意味著并發,而并發是困難的。

并發 01

想象有兩個線程嘗試修改同一個變量value:

情況一:線程1先到達

  1. 變量value的值變為”blah”。
  2. 然后當線程2到達時,變量value的值變為”blahy”。

情況二:線程2先到達

  1. 變量value的值變為”fluffy”。
  2. 然后當線程1到達時,值變為”blah”。

情況三:線程1與線程2交互

  1. 線程2得到值"fluff"然后賦給本地變量myValue。
  2. 線程1改變value的值為”blah”。
  3. 然后線程2醒來并把變量value的值改為”fluffy”

情況三顯然是唯一一個是錯誤的,除非除非你認為wiki編輯的幼稚做法是正確的(Google Code Wiki,我一直在關注你)。其他兩種情況主要是看你的意圖和想要達到的效果。線程2可能不會關心變量value的值是什么,主要的意圖就是在后面加上字符 ‘y'而不管它原來的值是什么,在這種前提下,情況一和情況二都是正確的。

但是如果線程2只是想把"fluff"改為”fluffy”,那么情況二和三都不正確。假定線程2想把值設為”fluffy”,有幾種辦法可以解決這個問題:

辦法一:悲觀鎖

(“No Entry”的標志對于在沒有在英國開車的人看得明白不?)

悲觀鎖和樂觀鎖這兩個詞通常在我們談論數據庫讀寫時經常會用到,但原理可以應用到在獲得一個對象的鎖的情況。

只要線程2一獲得Entry 的互斥鎖,它就會阻擊其它線程去改變它,然后它就可以隨意做它要做的事情,設置值,然后做其它事情。

你可以想象這里非常耗性能的,因為其它線程在系統各處徘徊著準備要獲得鎖然后又阻塞。線程越多,系統的響應性就會越慢.

辦法二:樂觀鎖

在這種情況,當線程2需要去寫Entry時才會去鎖定它.它需要檢查Entry自從上次讀過后是否已經被改過了。如果線程1在線程2讀完后到達并把值改為”blah”,線程2讀到了這個新值,線程2不會把"fluffy"寫到Entry里并把線程1所寫的數據覆蓋.線程2會重試(重新讀新的值,與舊值比較,如果相等則在變量的值后面附上’y’),這里在線程2不會關心新的值是什么的情況.或者線程2會拋出一個異常,或者會返回一個某些字段已更新的標志,這是在期望把”fluff”改為”fluffy”的情況.舉一個第二種情況的例子,如果你和另外一個用戶同時更新一個Wiki的頁面,你會告訴另外一個用戶的線程 Thread 2,它們需要重新加載從Thread1來新的變化,然后再提交它們的內容。

潛在的問題:死鎖

鎖定會帶來各種各樣的問題,比如死鎖,想象有2個線程需要訪問兩個資源

如果你濫用鎖技術,兩個鎖都在獲得鎖的情況下嘗試去獲得另外一個鎖,那就是你應該重啟你的電腦的時候了。(校注:作者還挺幽默)

很明確的一個問題:鎖技術是慢的..

關于鎖就是它們需要操作系統去做裁定。線程就像兩姐妹在為一個玩具在爭吵,然后操作系統就是能決定他們誰能拿到玩具的父母,就像當你跑向你父親告訴他你的姐姐在你玩著的時候搶走了你的變形金剛-他還有比你們爭吵更大的事情去擔心,他或許在解決你們爭吵之前要啟動洗碗機并把它擺在洗衣房里。如果你把你的注意力放在鎖上,不僅要花時間來讓操作系統來裁定。Disruptor論文中講述了我們所做的一個實驗。這個測試程序調用了一個函數,該函數會對一個 64位的計數器循環自增5億次。當單線程無鎖時,程序耗時300ms。如果增加一個鎖(仍是單線程、沒有競爭、僅僅增加鎖),程序需要耗時 10000ms,慢了兩個數量級。更令人吃驚的是,如果增加一個線程(簡單從邏輯上想,應該比單線程加鎖快一倍),耗時224000ms。使用兩個線程對計數器自增5億次比使用無鎖單線程慢1000倍。并發很難而鎖的性能糟糕。我僅僅是揭示了問題的表面,而且,這個例子很簡單。但重點是,如果代碼在多線程環境中執行,作為開發者將會遇到更多的困難:

  • 代碼沒有按設想的順序執行。上面的場景3表明,如果沒有注意到多線程訪問和寫入相同的數據,事情可能會很糟糕。
  • 減慢系統的速度。場景3中,使用鎖保護代碼可能導致諸如死鎖或者效率問題。

這就是為什么許多公司在面試時會多少問些并發問題(當然針對Java面試)。不幸的是,即使未能理解問題的本質或沒有問題的解決方案,也很容易學會如何回答這些問題。

#p#

Disruptor如何解決這些問題。

首先,Disruptor根本就不用鎖。

取而代之的是,在需要確保操作是線程安全的(特別是,在多生產者的環境下,更新下一個可用的序列號)地方,我們使用CAS(Compare And Swap/Set)操作。這是一個CPU級別的指令,在我的意識中,它的工作方式有點像樂觀鎖——CPU去更新一個值,但如果想改的值不再是原來的值,操作就失敗,因為很明顯,有其它操作先改變了這個值。

注意,這可以是CPU的兩個不同的核心,但不會是兩個獨立的CPU。

CAS操作比鎖消耗資源少的多,因為它們不牽涉操作系統,它們直接在CPU上操作。但它們并非沒有代價——在上面的試驗中,單線程無鎖耗時 300ms,單線程有鎖耗時10000ms,單線程使用CAS耗時5700ms。所以它比使用鎖耗時少,但比不需要考慮競爭的單線程耗時多。

回到Disruptor,在我講生產者時講過ClaimStrategy。在這些代碼中,你可以看見兩個策略,一個是SingleThreadedStrategy(單線程策略)另一個是 MultiThreadedStrategy(多線程策略)。你可能會有疑問,為什么在只有單個生產者時不用多線程的那個策略?它是否能夠處理這種場景?當然可以。但多線程的那個使用了AtomicLong(Java提供的CAS操作),而單線程的使用long,沒有鎖也沒有CAS。這意味著單線程版本會非常快,因為它只有一個生產者,不會產生序號上的沖突。

我知道,你可能在想:把一個數字轉成AtomicLong不可能是Disruptor速度快的唯一秘密。當然,它不是,否則它不可能稱為“為什么這么快(第一部分)”。

但這是非常重要的一點——在整個復雜的框架中,只有這一個地方出現多線程競爭修改同一個變量值。這就是秘密。還記得所有的訪問對象都擁有序號嗎?如果只有一個生產者,那么系統中的每一個序列號只會由一個線程寫入。這意味著沒有競爭、不需要鎖、甚至不需要CAS。在ClaimStrategy中,如果存在多個生產者,唯一會被多線程競爭寫入的序號就是 ClaimStrategy 對象里的那個。

這也是為什么Entry中的每一個變量都只能被一個消費者寫。它確保了沒有寫競爭,因此不需要鎖或者CAS。

回到為什么隊列不能勝任這個工作

因此你可能會有疑問,為什么隊列底層用RingBuffer來實現,仍然在性能上無法與 Disruptor 相比。隊列和最簡單的ring buffer只有兩個指針——一個指向隊列的頭,一個指向隊尾:

如果有超過一個生產者想要往隊列里放東西,尾指針就將成為一個沖突點,因為有多個線程要更新它。如果有多個消費者,那么頭指針就會產生競爭,因為元素被消費之后,需要更新指針,所以不僅有讀操作還有寫操作了。

等等,我聽到你喊冤了!因為我們已經知道這些了,所以隊列常常是單生產者和單消費者(或者至少在我們的測試里是)。

隊列的目的就是為生產者和消費者提供一個地方存放要交互的數據,幫助緩沖它們之間傳遞的消息。這意味著緩沖常常是滿的(生產者比消費者快)或者空的(消費者比生產者快)。生產者和消費者能夠步調一致的情況非常少見。

所以,這就是事情的真面目。一個空的隊列:

一個滿的隊列:

(校對注:這應該是一個雙向隊列)

隊列需要保存一個關于大小的變量,以便區分隊列是空還是滿。否則,它需要根據隊列中的元素的內容來判斷,這樣的話,消費一個節點(Entry)后需要做一次寫入來清除標記,或者標記節點已經被消費過了。無論采用何種方式實現,在頭、尾和大小變量上總是會有很多競爭,或者如果消費操作移除元素時需要使用一個寫操作,那元素本身也包含競爭。

基于以上,這三個變量常常在一個cache line里面,有可能導致false sharing。因此,不僅要擔心生產者和消費者同時寫size變量(或者元素),還要注意由于頭指針尾指針在同一位置,當頭指針更新時,更新尾指針會導致緩存不命中。這篇文章已經很長了,所以我就不再詳述細節了。

這就是我們所說的“分離競爭點問題”或者隊列的“合并競爭點問題”。通過將所有的東西都賦予私有的序列號,并且只允許一個消費者寫Entry對象中的變量來消除競爭,Disruptor 唯一需要處理訪問沖突的地方,是多個生產者寫入 Ring Buffer 的場景。

總結

Disruptor相對于傳統方式的優點:

  1. 沒有競爭=沒有鎖=非常快。
  2. 所有訪問者都記錄自己的序號的實現方式,允許多個生產者與多個消費者共享相同的數據結構。
  3. 在每個對象中都能跟蹤序列號(ring buffer,claim Strategy,生產者和消費者),加上神奇的cache line padding,就意味著沒有為偽共享和非預期的競爭。

校訂:需要注意Disruptor2.0使用了與本文中不一樣的名字。如果對類名感到困惑,請參考我的變更匯總。

原文鏈接:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-why-its-so-fast.html(有墻)

http://ifeve.com/disruptor-locks-are-bad/

譯文鏈接:http://ifeve.com/locks-are-bad/

 

責任編輯:陳四芳 來源: ifeve.com
相關推薦

2013-06-14 10:12:22

共享并行

2013-06-19 10:55:40

Disruptor并發框架

2013-06-18 10:30:45

Disruptor框架

2020-03-30 15:05:46

Kafka消息數據

2024-02-26 21:15:20

Kafka緩存參數

2020-02-27 15:44:41

Nginx服務器反向代理

2020-02-27 21:03:30

調度器架構效率

2022-01-04 08:54:32

Redis數據庫數據類型

2024-11-26 08:52:34

SQL優化Kafka

2023-08-29 07:46:08

Redis數據ReHash

2020-10-15 09:19:36

Elasticsear查詢速度

2021-05-27 20:56:51

esbuild 工具JavaScript

2012-08-17 10:01:07

云計算

2023-03-21 08:02:36

Redis6.0IO多線程

2019-02-18 08:10:53

2017-06-06 16:30:55

戴爾交付保障

2020-10-21 09:17:52

Redis面試內存

2020-04-27 07:13:37

Nginx底層進程

2023-11-02 10:22:29

gRPC后端通信

2021-03-18 14:34:34

達達集團京東云電商
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美精品久久 | 日韩av一区二区在线观看 | 精品亚洲一区二区 | 美女天天操 | 精品视频在线播放 | 国产精品免费一区二区三区 | 亚洲精品1 | 综合色婷婷 | 人成在线 | 很黄很污的网站 | 一区二区三区av | 欧美精品在线播放 | 免费激情网站 | 亚洲+变态+欧美+另类+精品 | 亚洲电影一级片 | 免费成人高清 | 色综合久久天天综合网 | 91精品国产色综合久久不卡98口 | 久久成人在线视频 | 在线不卡视频 | 亚洲伊人a| 国产亚洲精品精品国产亚洲综合 | 精品国产一区二区在线 | 欧美国产中文 | 免费在线观看毛片 | 亚洲国产一区二区三区 | 美女一区二区在线观看 | 日韩高清一区二区 | 在线成人精品视频 | 欧美视频三级 | 免费看91 | 国产视频一区在线 | 欧美国产精品一区二区三区 | 国产一区二区在线免费观看 | 亚洲欧洲成人av每日更新 | 干干天天| 久久久久久免费毛片精品 | 国产精品久久久久久久久免费 | 超碰操 | 紧缚调教一区二区三区视频 | 91大神在线看|