国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

細說.NET中的多線程(六使用MemoryBarrier,Volatile進行同步)

2019-11-14 13:49:22
字體:
來源:轉載
供稿:網友

上一節介紹了使用信號量進行同步,本節主要介紹一些非阻塞同步的方法。本節主要介紹MemoryBarrier,volatile,Interlocked。

MemoryBarriers

本文簡單的介紹一下這兩個概念,假設下面的代碼:

using System;class Foo{    int _answer;    bool _complete;    void A()    {        _answer = 123;        _complete = true;    }    void B()    {        if (_complete) Console.WriteLine(_answer);    }}

如果方法A和方法B同時在兩個不同線程中運行,控制臺可能輸出0嗎?答案是可能的,有以下兩個原因:

  • 編譯器,CLR或者CPU可能會更改指令的順序來提高性能
  • 編譯器,CLR或者CPU可能會通過緩存來優化變量,這種情況下對其他線程是不可見的。

最簡單的方式就是通過MemoryBarrier來保護變量,來防止任何形式的更改指令順序或者緩存。調用Thread.MemoryBarrier會生成一個內存柵欄,我們可以通過以下的方式解決上面的問題:

using System;using System.Threading;class Foo{    int _answer;    bool _complete;    void A()    {        _answer = 123;        Thread.MemoryBarrier();    // Barrier 1        _complete = true;        Thread.MemoryBarrier();    // Barrier 2    }    void B()    {        Thread.MemoryBarrier();    // Barrier 3        if (_complete)        {            Thread.MemoryBarrier();       // Barrier 4            Console.WriteLine(_answer);        }    }}

上面的例子中,barrier1和barrier3用來保證指令順序不會改變,barrier2和barrier4用來保證值變化不被緩存。一個好的處理方案就是我們在需要保護的變量前后分別加上MemoryBarrier。

在c#中,下面的操作都會生成MemoryBarrier:

  • Lock語句(Monitor.Enter,Monitor.Exit)
  • 所有Interlocked類的方法
  • 線程池的回調方法
  • Set或者Wait信號
  • 所有依賴于信號燈實現的方法,如starting或waiting 一個Task

因為上面這些行為,這段代碼實際上是線程安全的:

        int x = 0;        Task t = Task.Factory.StartNew(() => x++);        t.Wait();        Console.WriteLine(x);    // 1

在你自己的程序中,你可能重現不出來上面例子所說的情況。事實上,從msdn上對MomoryBarrier的解釋來看,只有對順序保護比較弱的多核系統才需要用到MomoryBarrier。但是有一點需要注意:多線程去修改變量并且不使用任何形似的鎖或者內存柵欄是會帶來一定的麻煩的。

下面一個例子能夠很好的說明上面的觀點(在你的VisualStudio中,選擇Release模式,并且Start Without Debugging重現這個問題):

        bool complete = false;        var t = new Thread(() =>        {            bool toggle = false;            while (!complete) toggle = !toggle;        });        t.Start();        Thread.Sleep(1000);        complete = true;        t.Join();        // Blocks indefinitely

這個程序永遠不會結束,因為complete變量被緩存在了CPU寄存器中。在while循環中加入Thread.MemoryBarrier可以解決這個問題。

volatile關鍵字

另外一種更高級的方式來解決上面的問題,那就是考慮使用volatile關鍵字。Volatile關鍵字告訴編譯器在每一次讀操作時生成一個fence,來實現保護保護變量的目的。具體說明可以參見msdn的介紹

VolatileRead和VolatileWrite

Volatile關鍵字只能加到類變量中。本地變量不能被聲明成volatile。這種情況你可以考慮使用System.Threading.Volatile.Read方法。我們看一下System.Threading.Volatile源碼如何實現這兩個方法的:

    public static bool Read(ref bool location)    {        bool flag = location;        Thread.MemoryBarrier();        return flag;    }    public static void Write(ref bool location, bool value)    {        Thread.MemoryBarrier();        location = value;    }

  

一目了然,通過MemoryBarrier來實現的,但是他只在讀操作的后面和寫操作的前面加了MemoryBarrier,那么你應該考慮,如果你先使用Volatile.Write再使用Volatile.Read是不是可能有問題呢?

c#中ConcurrentDictionary中使用了Volatile類來保護變量,有興趣的讀者可以看看c#的開發者是如何使用這個方法來保護變量的。

Interlocked

使用MemoryBarrier并不總是一個好的解決方案,尤其在不需要鎖的情況下。Interlocked方法提供了一些常用的原子操作來避免前面文章提到的一系列的問題。如使用Interlocked.Increment來替代++,Interlocked.Decrement來替代--。Msdn的文檔中詳細的介紹了相關的用法和原理。C#中的源碼里也經常能看見Interlocked相關的使用。

 

本文介紹了一些除了鎖和信號量之外的一些同步方式,歡迎批評與指正。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 无棣县| 西盟| 安仁县| 扶余县| 巴彦淖尔市| 松江区| 敖汉旗| 蓝田县| 攀枝花市| 昌吉市| 全南县| 峨眉山市| 彩票| 温泉县| 彰化市| 旅游| 南昌市| 新丰县| 化隆| 镶黄旗| 香格里拉县| 尉犁县| 汝州市| 桂东县| 神木县| 龙南县| 塔河县| 洪雅县| 南昌市| 邵武市| 汉阴县| 紫阳县| 甘泉县| 山阴县| 松江区| 湟源县| 新建县| 茂名市| 柳林县| 横山县| 三都|