自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

剖析Disruptor:為什么會這么快?(四)內(nèi)存屏障

開發(fā) 后端
我寫這個系列的博客主要目的是解析Disruptor是如何工作的,并深入了解下為什么這樣工作。理論上,我應(yīng)該從可能準(zhǔn)備使用disruptor的開發(fā)人員的角度來寫,以便在代碼和技術(shù)論文[Disruptor-1.0.pdf]之間搭建一座橋梁。這篇文章提及到了內(nèi)存屏障,我想弄清楚它們到底是什么,以及它們是如何應(yīng)用于實踐中的。

最近我博客文章更新有點慢,因為我在忙著寫一篇介紹內(nèi)存屏障(Memory Barries)以及如何將其應(yīng)用于Disruptor的文章。問題是,無論我翻閱了多少資料,向耐心的Martin和Mike請教了多少遍,以試圖理清一些知識點,可我總是不能直觀地抓到重點。大概是因為我不具備深厚的背景知識來幫助我透徹理解。

所以,與其像個傻瓜一樣試圖去解釋一些自己都沒完全弄懂的東西,還不如在抽象和大量簡化的層次上,把我在該領(lǐng)域所掌握的知識分享給大家 。Martin已經(jīng)寫了一篇文章《going into memory barriers》介紹內(nèi)存屏障的一些具體細(xì)節(jié),所以我就略過不說了。

免責(zé)聲明:文章中如有錯誤全由本人負(fù)責(zé),與Disruptor的實現(xiàn)和LMAX里真正懂這些知識的大牛們無關(guān)。

主題是什么?

我寫這個系列的博客主要目的是解析Disruptor是如何工作的,并深入了解下為什么這樣工作。理論上,我應(yīng)該從可能準(zhǔn)備使用disruptor的開發(fā)人員的角度來寫,以便在代碼和技術(shù)論文[Disruptor-1.0.pdf]之間搭建一座橋梁。這篇文章提及到了內(nèi)存屏障,我想弄清楚它們到底是什么,以及它們是如何應(yīng)用于實踐中的。

什么是內(nèi)存屏障?

它是一個CPU指令。沒錯,又一次,我們在討論CPU級別的東西,以便獲得我們想要的性能(Martin著名的 Mechanical Sympathy理論)?;旧希沁@樣一條指令: a)確保一些特定操作執(zhí)行的順序; b)影響一些數(shù)據(jù)的可見性(可能是某些指令執(zhí)行后的結(jié)果)。

編譯器和CPU可以在保證輸出結(jié)果一樣的情況下對指令重排序,使性能得到優(yōu)化。插入一個內(nèi)存屏障,相當(dāng)于告訴CPU和編譯器先于這個命令的必須先執(zhí)行,后于這個命令的必須后執(zhí)行。正如去拉斯維加斯旅途中各個站點的先后順序在你心中都一清二楚。

 

內(nèi)存屏障另一個作用是強制更新一次不同CPU的緩存。例如,一個寫屏障會把這個屏障前寫入的數(shù)據(jù)刷新到緩存,這樣任何試圖讀取該數(shù)據(jù)的線程將得到最新值,而不用考慮到底是被哪個cpu核心或者哪顆CPU執(zhí)行的。

和Java有什么關(guān)系?

現(xiàn)在我知道你在想什么——這不是匯編程序。它是Java。

這里有個神奇咒語叫volatile(我覺得這個詞在Java規(guī)范中從未被解釋清楚)。如果你的字段是volatile,Java內(nèi)存模型將在寫操作后插入一個寫屏障指令,在讀操作前插入一個讀屏障指令。

 

這意味著如果你對一個volatile字段進行寫操作,你必須知道:

1、一旦你完成寫入,任何訪問這個字段的線程將會得到最新的值。

2、在你寫入前,會保證所有之前發(fā)生的事已經(jīng)發(fā)生,并且任何更新過的數(shù)據(jù)值也是可見的,因為內(nèi)存屏障會把之前的寫入值都刷新到緩存。

舉個例子唄!

很高興你這樣說了。又是時候讓我來畫幾個甜甜圈了。

RingBuffer的指針(cursor)(譯注:指向隊尾元素)屬于一個神奇的volatile變量,同時也是我們能夠不用鎖操作就能實現(xiàn)Disruptor的原因之一。

 

生產(chǎn)者將會取得下一個Entry(或者是一批),并可對它(們)作任意改動, 把它(們)更新為任何想要的值。如你所知, 在所有改動都完成后,生產(chǎn)者對ring buffer調(diào)用commit方法來更新序列號(譯注:把cursor更新為該Entry的序列號)。對volatile字段(cursor)的寫操作創(chuàng) 建了一個內(nèi)存屏障,這個屏障將刷新所有緩存里的值(或者至少相應(yīng)地使得緩存失效)。

這時候,消費者們能獲得最新的序列號碼(8),并且因為內(nèi)存屏障保證了它之前執(zhí)行的指令的順序,消費者們可以確信生產(chǎn)者對7號Entry所作的改動已經(jīng)可用。

那么消費者那邊會發(fā)生什么?

消費者中的序列號是volatile類型的,會被若干個外部對象讀取——其他的下游消費者可能在跟蹤這個消費者。ProducerBarrier/RingBuffer(取決于你看的是舊的還是新的代碼)跟蹤它以確保環(huán)沒有出現(xiàn)重疊(wrap)的情況(譯注:為了防止下游的消費者和上游的消費者對同一個Entry競爭消費,導(dǎo)致在環(huán)形隊列中互相覆蓋數(shù)據(jù),下游消費者要對上游消費者的消費情況進行跟蹤)。

所以,如果你的下游消費者(C2)看見前一個消費者(C1)在消費號碼為12的Entry,當(dāng)C2的讀取也到了12,它在更新序列號前將可以獲得C1對該Entry的所作的更新。

基本來說就是,C1更新序列號前對ring buffer的所有操作(如上圖黑色所示),必須先發(fā)生,待C2拿到C1更新過的序列號之后,C2才可以為所欲為(如上圖藍(lán)色所示)。

對性能的影響

內(nèi)存屏障作為另一個CPU級的指令,沒有鎖那樣大的開銷。內(nèi)核并沒有在多個線程間干涉和調(diào)度。但凡事都是有代價的。內(nèi)存屏障的確是有開銷的——編譯器/cpu不能重排序指令,導(dǎo)致不可以盡可能地高效利用CPU,另外刷新緩存亦會有開銷。所以不要以為用volatile代替鎖操作就一點事都沒。

你會注意到Disruptor的實現(xiàn)對序列號的讀寫頻率盡量降到最低。對volatile字段的每次讀或?qū)懚际窍鄬Ω叱?本的操作。但是,也應(yīng)該認(rèn)識到在批量的情況下可以獲得很好的表現(xiàn)。如果你知道不應(yīng)對序列號頻繁讀寫,那么很合理的想到,先獲得一整批Entries,并在 更新序列號前處理它們。這個技巧對生產(chǎn)者和消費者都適用。以下的例子來自BatchConsumer:

  1. long nextSequence = sequence + 1
  2. while (running) 
  3.     try 
  4.     { 
  5.         final long availableSequence = consumerBarrier.waitFor(nextSequence); 
  6.         while (nextSequence <= availableSequence) 
  7.         { 
  8.             entry = consumerBarrier.getEntry(nextSequence); 
  9.             handler.onAvailable(entry); 
  10.             nextSequence++; 
  11.         } 
  12.         handler.onEndOfBatch(); 
  13.         sequence = entry.getSequence(); 
  14.     } 
  15.     … 
  16.     catch (final Exception ex) 
  17.     { 
  18.         exceptionHandler.handle(ex, entry); 
  19.         sequence = entry.getSequence(); 
  20.         nextSequence = entry.getSequence() + 1
  21.     } 

(你會注意到,這是個舊式的代碼和命名習(xí)慣,因為這是摘自我以前的博客文章,我認(rèn)為如果直接轉(zhuǎn)換為新式的代碼和命名習(xí)慣會讓人有點混亂)

在上面的代碼中,我們在消費者處理entries的循環(huán)中用一個局部變量(nextSequence)來遞增。這表明我們想盡可能地減少對volatile類型的序列號的進行讀寫。

總結(jié)

內(nèi)存屏障是CPU指令,它允許你對數(shù)據(jù)什么時候?qū)ζ渌M程可見作出假設(shè)。在Java里,你使用volatile關(guān)鍵字來實現(xiàn)內(nèi)存屏障。使用volatile意味著你不用被迫選擇加鎖,并且還能讓你獲得性能的提升。

但是,你需要對你的設(shè)計進行一些更細(xì)致的思考,特別是你對volatile字段的使用有多頻繁,以及對它們的讀寫有多頻繁。

PS:上文中講到的Disruptor中使用的New World Order 是一種完全不同于我目前為止所發(fā)表的博文中的命名習(xí)慣。我想下一篇文章會對舊式的和新式的命名習(xí)慣做一個對照。

原文鏈接:http://ifeve.com/disruptor-memory-barriers/

譯文鏈接:http://ifeve.com/disruptor-memory-barrier/

責(zé)任編輯:陳四芳 來源: ifeve.com
相關(guān)推薦

2013-06-14 10:12:22

共享并行

2013-06-17 14:41:10

Disruptor并發(fā)編程

2013-06-18 10:30:45

Disruptor框架

2020-03-30 15:05:46

Kafka消息數(shù)據(jù)

2020-02-27 21:03:30

調(diào)度器架構(gòu)效率

2020-02-27 15:44:41

Nginx服務(wù)器反向代理

2024-02-26 21:15:20

Kafka緩存參數(shù)

2020-10-15 09:19:36

Elasticsear查詢速度

2023-08-29 07:46:08

Redis數(shù)據(jù)ReHash

2021-05-27 20:56:51

esbuild 工具JavaScript

2012-08-17 10:01:07

云計算

2023-03-21 08:02:36

Redis6.0IO多線程

2020-04-27 07:13:37

Nginx底層進程

2021-03-18 14:34:34

達(dá)達(dá)集團京東云電商

2023-11-02 10:22:29

gRPC后端通信

2022-01-04 08:54:32

Redis數(shù)據(jù)庫數(shù)據(jù)類型

2024-07-24 08:38:07

2024-11-26 08:52:34

SQL優(yōu)化Kafka

2020-10-21 09:17:52

Redis面試內(nèi)存

2018-04-25 10:13:30

Redis內(nèi)存模型
點贊
收藏

51CTO技術(shù)棧公眾號