主頁 > .NET開發 > C# Interlocked 類

C# Interlocked 類

2022-11-04 06:01:12 .NET開發

【前言】

在日常開發作業中,我們經常要對變數進行操作,例如對一個int變數遞增++,在單執行緒環境下是沒有問題的,但是如果一個變數被多個執行緒操作,那就有可能出現結果和預期不一致的問題,

例如:

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

在單執行緒情況下執行,結果一定為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 這個結果是隨機的,和每個執行緒執行情況有關
}

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

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

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 方法可以輕松實作執行緒安全的變數自增

/// <summary>
/// thread safe increament
/// </summary>
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 內部代碼,我們只能通過查看原始碼來繼續學習,

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

找到 Interlocked 中的 ExchangeAdd 方法

image

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

我們繼續看內部實作

image

image

內部呼叫 InterlockedCompareExchange 函式,再往下就是直接呼叫的C++原始碼了

image

image

在這里將變數添加 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 這個函式著重提一下

image

可以看到這個函式沒有 32 位(int)型別的多載,為什么要單獨為 64 位的 long/ulong 型別單獨提供原子性讀取運算子呢?

這是因為CPU有 32 位處理器和 64 位處理器,在 64 位處理器上,暫存器一次處理的資料寬度是 64 位,因此在 64 位處理器和 64 位作業系統上運行的程式,可以一次性讀取 64 位數值,

但是在 32 位處理器和 32 位作業系統情況下,long/ulong 這種數值,則要分成兩步操作來進行,分別讀取 32 位資料后,再合并在一起,那顯然就會出現多執行緒情況下的并發問題,

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

image

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

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

【關于性能】

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

我們同樣用執行緒池調度50個Task(內部可能執行緒重用),分別執行 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 方式要好一些

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

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

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

Visual Studio 自帶的分析工具,查看執行緒使用率

使用 Process Explorer 工具查看代碼執行程序中背景關系切換數

可以大概估計出,采用 lock(Monitor)同步自增方式,背景關系切換 243

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

Visual Studio 自帶的分析工具,查看執行緒使用率

使用 Process Explorer 工具查看代碼執行程序中背景關系切換數

可以大概估計出,采用 CAS 自增方式,背景關系切換 220

可見,不論使用什么技術手段,執行緒創建太多都會帶來大量的執行緒背景關系切換

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

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

【總結】

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

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

【原始碼地址】

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

【博主宣告】

本文為站主原創作品,轉載請注明出處:http://www.cnblogs.com/7tiny 且在文章頁面明顯位置給出原文鏈接,
作者:

7tiny
Software Development
北京市海淀區 Haidian Area Beijing 100089,P.R.China
郵箱Email : [email protected]  
網址Http: http://www.7tiny.com
WeChat: seven-tiny
更多聯系方式點我哦~


Best Regard ~

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/526738.html

標籤:C#

上一篇:更新物件時如何使用Linq進行左外連接?

下一篇:快速創建軟體安裝包-ClickOnce

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more