真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網站制作重慶分公司

C# Interlocked 類

【前言】

在日常開發(fā)工作中,我們經常要對變量進行操作,例如對一個int變量遞增++。在單線程環(huán)境下是沒有問題的,但是如果一個變量被多個線程操作,那就有可能出現結果和預期不一致的問題。

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供海曙網站建設、海曙做網站、海曙網站設計、海曙網站制作等企業(yè)網站建設、網頁設計與制作、海曙企業(yè)網站模板建站服務,十余年海曙做網站經驗,不只是建網站,更提供有價值的思路和整體網絡服務。

例如:

static void Main(string[] args)
{
    var j = 0;
    for (int i = 0; i < 100; i++)
    {
        j++;
    }
    Console.WriteLine(j);
    //100
}

在單線程情況下執(zhí)行,結果一定為100,那么在多線程情況下呢?

static void Main(string[] args)
{
    var j = 0;
    var t1 = Task.Run(() =>
    {
        for (int i = 0; i < 50000; i++)
        {
            j++;
        }
    });
    var t2 = Task.Run(() =>
    {
        for (int i = 0; i < 50000; i++)
        {
            j++;
        }
    });
    Task.WaitAll(t1, t2);
    Console.WriteLine(j);
    //82869 這個結果是隨機的,和每個線程執(zhí)行情況有關
}

我們可以看到,多線程情況下并不能保證執(zhí)行正確,我們也將這種情況稱為 “非線程安全”

這種情況下我們可以通過加鎖來達到線程安全的目的

static void Main(string[] args)
{
    var locker = new object();
    var j = 0;
    var t1 = Task.Run(() =>
    {
        for (int i = 0; i < 50000; i++)
        {
            lock (locker)
            {
                j++;
            }
        }
    });
    var t2 = Task.Run(() =>
    {
        for (int i = 0; i < 50000; i++)
        {
            lock (locker)
            {
                j++;
            }
        }
    });
    Task.WaitAll(t1, t2);
    Console.WriteLine(j);
    //100000 這里是一定的
}

加鎖的確能解決上述問題,那么有沒有一種更加輕量級,更加簡潔的寫法呢?

那么,今天我們就來認識一下 Interlocked 類

【Interlocked 類下的方法】

Increment(ref int location)

Increment 方法可以輕松實現線程安全的變量自增

/// 
/// thread safe increament
/// 
public static void Increament()
{
    var j = 0;

    Task.WaitAll(
        Enumerable.Range(0, 50)
        .Select(t =>
            Task.Run(() =>
            {
                for (int i = 0; i < 2000; i++)
                {
                    Interlocked.Increment(ref j);
                }
            }
        ))
        .ToArray()
        );

    Console.WriteLine($"multi thread increament result={j}");
    //result=100000
}

看到這里,我們一定好奇這個方法底層是怎么實現的?

我們通過ILSpy反編譯查看源碼:

首先看到 Increment 方法其實是通過調用 Add 方法來實現自增的

再往下看,Add 方法是通過 ExchangeAdd 方法來實現原子性的自增,因為該方法返回值是增加前的原值,因此返回時增加了本次新增的,結果便是相加的結果,當然 location1 變量已經遞增成功了,這里只是為了友好地返回增加后的結果。

我們再往下看

這個方法用 [MethodImpl(MethodImplOptions.InternalCall)] 修飾,表明這里調用的是 CLR 內部代碼,我們只能通過查看源碼來繼續(xù)學習。

我們打開 dotnetcore 源碼:https://github.com/dotnet/corefx

找到 Interlocked 中的 ExchangeAdd 方法

可以看到,該方法用循環(huán)不斷自旋賦值并檢查是否賦值成功(CompareExchange返回的是修改前的值,如果返回結果和修改前結果是一致,則說明修改成功)

我們繼續(xù)看內部實現

內部調用 InterlockedCompareExchange 函數,再往下就是直接調用的C++源碼了

在這里將變量添加 volatile 修飾符,阻止寄存器緩存變量值(關于volatile不在此贅述),然后直接調用了C++底層內部函數 __sync_val_compare_and_swap 實現原子性的比較交換操作,這里直接用的是 CPU 指令進行原子性操作,性能非常高。

相同機制函數

Increment 函數機制類似,Interlocked 類下的大部分方法都是通過 CompareExchange 底層函數來操作的,因此這里不再贅述

  • Add 添加值
  • CompareExchange 比較交換
  • Decrement 自減
  • Exchange 交換
  • And 按位與
  • Or 按位或
  • Read 讀64位數值

public static long Read(ref long location)

Read 這個函數著重提一下

可以看到這個函數沒有 32 位(int)類型的重載,為什么要單獨為 64 位的 long/ulong 類型單獨提供原子性讀取操作符呢?

這是因為CPU有 32 位處理器和 64 位處理器,在 64 位處理器上,寄存器一次處理的數據寬度是 64 位,因此在 64 位處理器和 64 位操作系統(tǒng)上運行的程序,可以一次性讀取 64 位數值。

但是在 32 位處理器和 32 位操作系統(tǒng)情況下,long/ulong 這種數值,則要分成兩步操作來進行,分別讀取 32 位數據后,再合并在一起,那顯然就會出現多線程情況下的并發(fā)問題。

因此這里提供了原子性的方法來應對這種情況。

這里底層同樣用了 CompareExchange 操作來保證原子性,參數這里就給了兩個0,可以兼容如果原值是 0 則寫入 0 ,如果原值非 0 則不寫入,返回原值。

__sync_val_compare_and_swap 函數
在寫入新值之前, 讀出舊值, 當且僅當舊值與存儲中的當前值一致時,才把新值寫入存儲

【關于性能】

多線程下實現原子性操作方式有很多種,我們一定會關心在不同場景下,不同方法間的性能問題,那么我們簡單來對比下 Interlocked 類提供的方法和 lock 關鍵字的性能對比

我們同樣用線程池調度50個Task(內部可能線程重用),分別執(zhí)行 200000 次自增運算

public static void IncreamentPerformance()
{
    //lock method

    var locker = new object();

    var stopwatch = new Stopwatch();

    stopwatch.Start();

    var j1 = 0;

    Task.WaitAll(
        Enumerable.Range(0, 50)
        .Select(t =>
            Task.Run(() =>
            {
                for (int i = 0; i < 200000; i++)
                {
                    lock (locker)
                    {
                        j1++;
                    }
                }
            }
        ))
        .ToArray()
        );

    Console.WriteLine($"Monitor lock,result={j1},elapsed={stopwatch.ElapsedMilliseconds}");

    stopwatch.Restart();

    //Increment method

    var j2 = 0;

    Task.WaitAll(
        Enumerable.Range(0, 50)
        .Select(t =>
            Task.Run(() =>
            {
                for (int i = 0; i < 200000; i++)
                {
                    Interlocked.Increment(ref j2);
                }
            }
        ))
        .ToArray()
        );

    stopwatch.Stop();

    Console.WriteLine($"Interlocked.Increment,result={j2},elapsed={stopwatch.ElapsedMilliseconds}");
}

運算結果

可以看到,采用 Interlocked 類中的自增函數,性能比 lock 方式要好一些

雖然這里看起來性能要好,但是不同的業(yè)務場景要針對性思考,采用恰當的編碼方式,不要一味追求性能

我們簡單分析下造成執(zhí)行時間差異的原因

我們都知道,使用lock(底層是Monitor類),在上述代碼中會阻塞線程執(zhí)行,保證同一時刻只能有一個線程執(zhí)行 j1++ 操作,因此能保證操作的原子性,那么在多核CPU下,也只能有一個CPU核心在執(zhí)行這段邏輯,其他核心都會等待或執(zhí)行其他事件,線程阻塞后,并不會一直在這里傻等,而是由操作系統(tǒng)調度執(zhí)行其他任務。由此帶來的代價可能是頻繁的線程上下文切換,并且CPU使用率不會太高,我們可以用分析工具來印證下。

Visual Studio 自帶的分析工具,查看線程使用率

使用 Process Explorer 工具查看代碼執(zhí)行過程中上下文切換數

可以大概估計出,采用 lock(Monitor)同步自增方式,上下文切換 243

那么我們用同樣的方式看下底層用 CAS 函數執(zhí)行自增的開銷

Visual Studio 自帶的分析工具,查看線程使用率

使用 Process Explorer 工具查看代碼執(zhí)行過程中上下文切換數

可以大概估計出,采用 CAS 自增方式,上下文切換 220

可見,不論使用什么技術手段,線程創(chuàng)建太多都會帶來大量的線程上下文切換

這個應該是和測試的代碼相關

兩者比較大的區(qū)別在CPU的使用率上,因為 lock 方式會造成線程阻塞,因此不會所有的CPU核心同時參與運算,CPU在當前進程上使用率不會太高,但 cas 方式CPU在自己的時間分片內并沒有被阻塞或重新調度,而是不停地執(zhí)行比較替換的動作(其實這種場景算是無用功,不必要的負開銷),造成CPU使用率非常高。

【總結】

簡單來說,Interlocked 類提供的方法給我們帶來了方便快捷操作字段的方式,比起使用鎖同步的編程方式來說,要輕量不少,執(zhí)行效率也大大提高。但是該技術并非銀彈,一定要考慮清楚使用的場景后再決定使用,比如服務器web應用下,多線程執(zhí)行大量耗費CPU的運算,可能會嚴重影響應用吞吐量。雖然表面看起來執(zhí)行這個單一的任務效率高一些(代價是CPU全部撲在這個任務上,無法響應其他任務),其實在我們的測試中,總共執(zhí)行了 10000000 次運算,這種場景應該是比較極端的,而且在web應用場景下,用 lock 的方式響應時間也沒有達到不能容忍的程度,但是用 lock 的好處是cpu可以處理其他用戶請求的任務,極大提高了吞吐量。

我們建議在競爭較少的場景,或者不需要很高吞吐量的場景下(簡單說是CPU時間不那么寶貴的場景下)我們可以用 Interlocked 類來保證操作的原子性,可以適當提升性能。而在競爭非常激烈的場景下,一定不要用 Interlocked 來處理原子性操作,改用 lock 方式會好很多。

【源碼地址】

https://github.com/sevenTiny/CodeArts/blob/master/CSharp/ConsoleAppNet60/InterlockedTest.cs


網站題目:C# Interlocked 類
文章網址:http://weahome.cn/article/dsoiojs.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部