一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

再說 lock-free 編程

  lock-free  編程實(shí)在讓人又愛又恨。博主以前曾經(jīng)寫過幾篇關(guān)于 lock-free 編程的文章。比如關(guān)于無鎖編程并發(fā)數(shù)據(jù)結(jié)構(gòu):迷人的原子。如果想更加深入的了解和實(shí)踐 lock-free 編程,可以參考CLR 2.0 Memory Model并發(fā)數(shù)據(jù)結(jié)構(gòu):Stack。這篇文章并不打算繼續(xù)闡述如何使用 lock-free 技術(shù),而是談一下它的負(fù)面影響。從而讓大家對(duì) lock-free 有個(gè)更加全面的認(rèn)識(shí)。

  說到 lock-free 編程,現(xiàn)實(shí)中經(jīng)常使用 CAS 原語。CAS 是英文 Compare and Swap 的簡(jiǎn)寫。在 Windows 和 .NET 平臺(tái),由于歷史原因,它被寫做 Interlocked API。原子操作在 x86 架構(gòu) CPU 對(duì)應(yīng)的匯編指令有 XCHG、CMPXCHG、INC 等,當(dāng)然還得加上 LOCK 作為前綴(更多信息請(qǐng)看 并發(fā)數(shù)據(jù)結(jié)構(gòu):迷人的原子)。

  CAS 原語在輕度和中度爭(zhēng)用情況下確實(shí)可以大幅度提高程序性能。但凡事有利必有弊,CAS 原語極度扼殺了程序的可伸縮性(其他缺點(diǎn)請(qǐng)看關(guān)于無鎖編程)。各位看官可能覺得這種觀點(diǎn)有點(diǎn)偏激,但事實(shí)如此。請(qǐng)容博主細(xì)細(xì)道來:

  • CAS 的原子性完全取決于硬件實(shí)現(xiàn)。大多數(shù) Intel 和 AMD 的 CPU 采用了一種叫做 MOSEI 緩存一致性協(xié)議來管理緩存。這種架構(gòu)下,處理器緩存內(nèi) CAS 操作相對(duì)成本低廉。但一旦資源爭(zhēng)用,就會(huì)引起緩存失效和總線占用。緩存越失效,總線越被占用,完成 CAS 操作也越被延遲。緩存爭(zhēng)用是程序可伸縮性殺手。當(dāng)然對(duì)于非 CAS 內(nèi)存操作來說也是如此,但 CAS 情況更加槽糕。
  • CAS 操作要比普通內(nèi)存操作花費(fèi)更多 CPU 周期。這歸功于緩存分級(jí)的額外負(fù)擔(dān)、刷新寫緩沖區(qū)與穿越內(nèi)存柵欄限制和需求以及編譯器對(duì) CAS 操作優(yōu)化的能力。
  • CAS 經(jīng)常被用在優(yōu)化并行操作上。這意味著 CAS 操作失敗將導(dǎo)致重新嘗試某些指令(典型的回滾操作)。即便沒有任何爭(zhēng)用,它也會(huì)做一些無用功。不論成功或失敗都會(huì)增加爭(zhēng)用的風(fēng)險(xiǎn)。

  大多數(shù) CAS 操作發(fā)生在鎖進(jìn)入和退出時(shí)。盡管鎖可由單一 CAS 操作構(gòu)建,但 .NET CLR Monitor 類卻使用了兩個(gè)(一個(gè)在 Enter 方法,另一個(gè)在 Exit 方法)。lock-free 算法也經(jīng)常使用 CAS 原語來代替使用鎖機(jī)制。但是由于內(nèi)存重組,這樣的算法也常常需要顯式的柵欄,即便使用了 CAS 指令。鎖機(jī)制非常邪惡,但大多數(shù)合格的開發(fā)人員都知道讓鎖持有盡量少的時(shí)間。因此,雖然鎖機(jī)制讓人非常討厭,且影響性能。但相對(duì)于大量,頻繁的 CAS 操作而言,它卻并不影響程序的可伸縮性。

  舉個(gè)很簡(jiǎn)單的例子,增加計(jì)數(shù) 100,000,000 次。要做到這樣,有幾種方式。如果僅運(yùn)行在單核單處理器上,我們可以使用普通的內(nèi)存操作:

static volatile int counter = 0;static void BaselineCounter(){    for (int i = 0; i < Count; i++)    {        counter++;    }}

  很明顯,上述代碼示例不是線程安全的,但給計(jì)數(shù)器提供了一個(gè)很好的時(shí)間基準(zhǔn)。下面我們使用 LOCK INC 來作為線程安全的第一種方式:

static volatile int counter = 0;static void LockIncCounter(){    for (int i = 0; i < Count; i++)    {        Interlocked.Increment(ref counter);    }}

 

  現(xiàn)在代碼示例線程安全了。我們還可以采取另外一種方式來保證線程安全。如果需要執(zhí)行一些驗(yàn)證(比如內(nèi)存溢出保護(hù)),我們通常會(huì)使用這種方式。就是使用 CMPXCHG(即 CAS):

static volatile int counter = 0;static void CASCounter(){    for (int i = 0; i < Count; i++)    {        int oldValue;        do        {            oldValue = counter;        }        while (Interlocked.CompareExchange(ref counter, oldValue + 1, oldValue) != oldValue);    }}

  現(xiàn)在問一個(gè)有意思的問題:當(dāng)緩存爭(zhēng)用時(shí),哪一個(gè)方法更慢?結(jié)果可能會(huì)讓你大吃一驚哦。

  在 Intel 4 核處理器下測(cè)試結(jié)果如下:

F1

  圖中,當(dāng) CPU 使用 2 個(gè)核時(shí),BaselineCounter 方法是單核單路情況的 2.11 倍。其他情況類似。通過結(jié)果比對(duì),我們可以得知:更多的并發(fā)性導(dǎo)致結(jié)果更加槽糕。這很大部分原因由內(nèi)存爭(zhēng)用所致。

  當(dāng) CAS 操作失敗,通過旋轉(zhuǎn)等待可以改善 CASCounter 方法的在多核處理器上的性能(具體技巧可以參考夏天是個(gè)好季節(jié)兄的自己動(dòng)手實(shí)現(xiàn)一個(gè)輕量級(jí)的信號(hào)量(一)(二))。這可以大大減少活鎖和關(guān)聯(lián)內(nèi)聯(lián)阻礙鎖耗費(fèi)的時(shí)間。

  當(dāng)然,這個(gè)示例非常極端。它頻繁反復(fù)修改同一個(gè)內(nèi)存地址。通過期間插入特定的函數(shù)調(diào)用,延遲訪問共享內(nèi)存可以極大緩解壓力。

  比如插入 2 個(gè)函數(shù)調(diào)用,我們得到了如下數(shù)據(jù):

F2  插入 64 個(gè)函數(shù)調(diào)用之后,數(shù)據(jù)又變成了如下所示:

F3   這個(gè)時(shí)候,我們看到多核所花費(fèi)的時(shí)間少于單核了。這就是我們使用并行所帶來的加速。看到這里,我們可能會(huì)想,既然從 2 到 64 個(gè)函數(shù)調(diào)用使得結(jié)果越來越好,那么超過 64 個(gè)函數(shù)調(diào)用豈不是會(huì)變得更好?實(shí)際上,在插入 128 個(gè)函數(shù)調(diào)用之后,加速已經(jīng)達(dá)到極限。結(jié)果如下所示:

F4   如何計(jì)算加速比,請(qǐng)參考并行思維 [II]

  天下沒有免費(fèi)的午餐,CAS 也不例外。我們應(yīng)當(dāng)慎之又慎的將 lock-free CAS 代碼放到我們的代碼中,且必須清楚的知道線程執(zhí)行它們的頻繁程度。我們可以用下面這句話來作為總結(jié):共享是魔鬼。它從根本上限制應(yīng)用程序可伸縮性,最好盡量避免。共享內(nèi)存需要并發(fā)控制,而并發(fā)控制需要 CAS。CAS 又非常昂貴,因此共享內(nèi)存也非常昂貴。有很多人提出 lock-free 技術(shù),事務(wù)內(nèi)存,讀寫鎖等可以改善程序可伸縮性。但很遺憾,這種情況很少出現(xiàn)。CAS 往往比正確實(shí)現(xiàn)鎖機(jī)制的解決方案更加糟糕。很大原因要?dú)w結(jié)于共享內(nèi)存、樂觀失敗嘗試、緩存失效等。

overred 兄在 review 這篇文章的時(shí)候,提了一個(gè)很好的問題:在使用 Interlocked API 的時(shí)候,共享變量不用 volatile 修飾。

  為了更方便說明這個(gè)問題,俺寫個(gè)簡(jiǎn)單點(diǎn)的代碼示例,如下所示:

using System;namespace Lucifer.CSharp.Sample{    class Program    {        static volatile int x;        static void Main(string[] args)        {            Foo(ref x);        }        static void Foo(ref int y)        {            while (y == 0) ;        }    }}

  當(dāng)我們?cè)?Visual Studio 中編譯這段代碼時(shí),IDE 會(huì)給出編譯警告,如下所示:

F5  通常來說,我們對(duì)于這樣的編譯警告應(yīng)該給予足夠重視。比如在上面的例子中,JIT 編譯器會(huì)認(rèn)為 y 一直未變,從而引起死循環(huán)。在 IA64 平臺(tái)上,這會(huì)被認(rèn)為普通內(nèi)存訪問代替了特殊的 load-acquire 訪問,這就可能導(dǎo)致 CPU 指令重組方面的一些 Bug。但是有一種情況例外,就是使用 Interlocked API 和 Thread.VolatileXXX 方法以及鎖。因?yàn)檫@些 API 內(nèi)部都會(huì)顯式要求內(nèi)存柵欄和硬件原子指令,而不管外部共享變量是否采用 volatile 修飾。因此,文中采用的測(cè)試方法還是很安全嘀。

  如果你覺得這個(gè)編譯警告很煩人,可以使用 #pragma 指令禁掉這種警告,如下所示:

static volatile int x;static void Foo(){#pragma warning disable 0420    Interlocked.Exchange(ref x, 1);#pragma warning restore 0420}

  當(dāng)然,也可以完全不用 volatile 修飾符。CLR 內(nèi)存模型保證了這一點(diǎn)。

  如何正確使用 volatile ,請(qǐng)參考并發(fā)數(shù)據(jù)結(jié)構(gòu):談?wù)剉olatile變量

NET技術(shù)再說 lock-free 編程,轉(zhuǎn)載需保留來源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 黄色片视频免费 | 超清乱人伦中文视频在线 | 成人免费xxx在线观看 | 美女被男人扒开腿猛网站 | 激情五月亚洲 | 中文字幕在线视频网 | 一本加勒比hezyo东京re高清 | 国产视频亚洲 | 涩涩在线视频 | 国产精品视频一区二区三区 | 国产精品深夜福利免费观看 | 日本精品久久久久中文字幕 1 | 天天拍夜夜添久久精品中文 | 亚洲欧洲免费视频 | 搞黄网站在线观看 | 91麻豆精品国产综合久久久 | yiren22亚洲综合高清一区 | 久草国产精品 | 91国在线产| 四虎影永久在线观看网址 | 免费一级乱子伦片 | 成人午夜毛片在线看 | 韩国一级爽快片淫片高清 | 综合精品| 欧美中文字幕一区二区三区 | 色偷偷91久久综合噜噜噜 | 免费一区二区三区四区五区 | 久久精品国产夜色 | 欧美黑人xxxxx性受 | 免费亚洲网站 | 91麻豆精品国产自产在线观看一区 | 精品日本久久久久久久久久 | 99精品伊人久久久大香线蕉 | 色久月| 国产一区国产二区国产三区 | 国产成人深夜福利短视频99 | 激情五月六月婷婷 | 99在线精品视频在线观看 | 国产乱子伦 | 色哟哟www视频在线观看高清 | 米奇7777狠狠狠狠视频影院 |