亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C#多線程系列之原子操作

 更新時(shí)間:2022年02月13日 11:55:55   作者:癡者工良  
本文詳細(xì)講解了C#多線程的原子操作,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

知識(shí)點(diǎn)

競(jìng)爭(zhēng)條件

當(dāng)兩個(gè)或兩個(gè)以上的線程訪問共享數(shù)據(jù),并且嘗試同時(shí)改變它時(shí),就發(fā)生爭(zhēng)用的情況。它們所依賴的那部分共享數(shù)據(jù),叫做競(jìng)爭(zhēng)條件。

數(shù)據(jù)爭(zhēng)用是競(jìng)爭(zhēng)條件中的一種,出現(xiàn)競(jìng)爭(zhēng)條件可能會(huì)導(dǎo)致內(nèi)存(數(shù)據(jù))損壞或者出現(xiàn)不確定性的行為。

線程同步

如果有 N 個(gè)線程都會(huì)執(zhí)行某個(gè)操作,當(dāng)一個(gè)線程正在執(zhí)行這個(gè)操作時(shí),其它線程都必須依次等待,這就是線程同步。

多線程環(huán)境下出現(xiàn)競(jìng)爭(zhēng)條件,通常是沒有執(zhí)行正確的同步而導(dǎo)致的。

CPU時(shí)間片和上下文切換

時(shí)間片(timeslice)是操作系統(tǒng)分配給每個(gè)正在運(yùn)行的進(jìn)程微觀上的一段 CPU 時(shí)間。

首先,內(nèi)核會(huì)給每個(gè)進(jìn)程分配相等的初始時(shí)間片,然后每個(gè)進(jìn)程輪番地執(zhí)行相應(yīng)的時(shí)間,當(dāng)所有進(jìn)程都處于時(shí)間 片耗盡的狀態(tài)時(shí),內(nèi)核會(huì)重新為每個(gè)進(jìn)程計(jì)算并分配時(shí)間片,如此往復(fù)。

請(qǐng)參考:https://zh.wikipedia.org/wiki/%E6%97%B6%E9%97%B4%E7%89%87

上下文切換(Context Switch),也稱做進(jìn)程切換或任務(wù)切換,是指 CPU 從一個(gè)進(jìn)程或線程切換到另一個(gè)進(jìn)程或線程。

在接受到中斷(Interrupt)的時(shí)候,CPU 必須要進(jìn)行上下文交換。進(jìn)行上下文切換時(shí),會(huì)帶來性能損失。

請(qǐng)參考[https://zh.wikipedia.org/wiki/上下文交換

阻塞

阻塞狀態(tài)指線程處于等待狀態(tài)。當(dāng)線程處于阻塞狀態(tài)時(shí),會(huì)盡可能少占用 CPU 時(shí)間。

當(dāng)線程從運(yùn)行狀態(tài)(Runing)變?yōu)樽枞麪顟B(tài)時(shí)(WaitSleepJoin),操作系統(tǒng)就會(huì)將此線程占用的 CPU 時(shí)間片分配給別的線程。當(dāng)線程恢復(fù)運(yùn)行狀態(tài)時(shí)(Runing),操作系統(tǒng)會(huì)重新分配 CPU 時(shí)間片。

分配 CPU 時(shí)間片時(shí),會(huì)出現(xiàn)上下文切換。

內(nèi)核模式和用戶模式

只有操作系統(tǒng)才能切換線程、掛起線程,因此阻塞線程是由操作系統(tǒng)處理的,這種方式被稱為內(nèi)核模式(kernel-mode)。

Sleep()、Join() 等,都是使用內(nèi)核模式來阻塞線程,實(shí)現(xiàn)線程同步(等待)。

內(nèi)核模式實(shí)現(xiàn)線程等待時(shí),出現(xiàn)上下文切換。這適合等待時(shí)間比較長(zhǎng)的操作,這樣會(huì)減少大量的 CPU 時(shí)間損耗。

如果線程只需要等待非常微小的時(shí)間,阻塞線程帶來的上下文切換代價(jià)會(huì)比較大,這時(shí)我們可以使用自旋,來實(shí)現(xiàn)線程同步,這一方法稱為用戶模式(user-mode)。

Interlocked 類

為多個(gè)線程共享的變量提供原子操作。

使用 Interlocked 類,可以在不阻塞線程(lock、Monitor)的情況下,避免競(jìng)爭(zhēng)條件。

Interlocked 類是靜態(tài)類,讓我們先來看看 Interlocked 的常用方法:

方法作用
CompareExchange()比較兩個(gè)數(shù)是否相等,如果相等,則替換第一個(gè)值。
Decrement()以原子操作的形式遞減指定變量的值并存儲(chǔ)結(jié)果。
Exchange()以原子操作的形式,設(shè)置為指定的值并返回原始值。
Increment()以原子操作的形式遞增指定變量的值并存儲(chǔ)結(jié)果。
Add()對(duì)兩個(gè)數(shù)進(jìn)行求和并用和替換第一個(gè)整數(shù),上述操作作為一個(gè)原子操作完成。
Read()返回一個(gè)以原子操作形式加載的值。

全部方法請(qǐng)查看:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.interlocked?view=netcore-3.1#methods

1,出現(xiàn)問題

問題:

? C# 中賦值和一些簡(jiǎn)單的數(shù)學(xué)運(yùn)算不是原子操作,受多線程環(huán)境影響,可能會(huì)出現(xiàn)問題。

我們可以使用 lock 和 Monitor 來解決這些問題,但是還有沒有更加簡(jiǎn)單的方法呢?

首先我們編寫以下代碼:

        private static int sum = 0;
        public static void AddOne()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                sum += 1;
            }
        }

這個(gè)方法的工作完成后,sum 會(huì) +100。

我們?cè)?Main 方法中調(diào)用:

        static void Main(string[] args)
        {
            AddOne();
            AddOne();
            AddOne();
            AddOne();
            AddOne();
            Console.WriteLine("sum = " + sum);
        }

結(jié)果肯定是 5000000,無可爭(zhēng)議的。

但是這樣會(huì)慢一些,如果作死,要多線程同時(shí)執(zhí)行呢?

好的,Main 方法改成如下:

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(AddOne);
                thread.Start();
            }

            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("sum = " + sum);
        }

筆者運(yùn)行一次,出現(xiàn)了 sum = 2633938

我們將每次運(yùn)算的結(jié)果保存到數(shù)組中,截取其中一段發(fā)現(xiàn):

8757
8758
8760
8760
8760
8761
8762
8763
8764
8765
8766
8766
8768
8769

多個(gè)線程使用同一個(gè)變量進(jìn)行操作時(shí),并不知道此變量已經(jīng)在其它線程中發(fā)生改變,導(dǎo)致執(zhí)行完畢后結(jié)果不符合期望。

我們可以通過下面這張圖來解釋:

因此,這里就需要原子操作,在某個(gè)時(shí)刻,必須只有一個(gè)線程能夠進(jìn)行某個(gè)操作。而上面的操作,指的是讀取、計(jì)算、寫入這一過程。

當(dāng)然,我們可以使用 lock 或者 Monitor 來解決,但是這樣會(huì)帶來比較大的性能損失。

這時(shí) Interlocked 就起作用了,對(duì)于一些簡(jiǎn)單的操作運(yùn)算, Interlocked 可以實(shí)現(xiàn)原子性的操作。

實(shí)現(xiàn)原子性,可以通過多種鎖來解決,目前我們學(xué)習(xí)到了 lock、Monitor,現(xiàn)在來學(xué)習(xí) Interlocked ,后面會(huì)學(xué)到更加多的鎖的實(shí)現(xiàn)。

2,Interlocked.Increment()

用于自增操作。

我們修改一下 AddOne 方法:

        public static void AddOne()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                Interlocked.Increment(ref sum);
            }
        }

然后運(yùn)行,你會(huì)發(fā)現(xiàn)結(jié)果 sum = 5000000 ,這就對(duì)了。

說明 Interlocked 可以對(duì)簡(jiǎn)單值類型進(jìn)行原子操作。

Interlocked.Increment() 是遞增,而 Interlocked.Decrement() 是遞減。

3,Interlocked.Exchange()

Interlocked.Exchange() 實(shí)現(xiàn)賦值運(yùn)算。

這個(gè)方法有多個(gè)重載,我們找其中一個(gè)來看看:

public static int Exchange(ref int location1, int value);

意思是將 value 賦給 location1 ,然后返回 location1 改變之前的值。

測(cè)試:

        static void Main(string[] args)
        {
            int a = 1;
            int b = 5;

            // a 改變前為1
            int result1 = Interlocked.Exchange(ref a, 2);

            Console.WriteLine($"a新的值 a = {a}   |  a改變前的值 result1 = {result1}");

            Console.WriteLine();

            // a 改變前為 2,b 為 5
            int result2 = Interlocked.Exchange(ref a, b);

            Console.WriteLine($"a新的值 a = {a}   | b不會(huì)變化的  b =    |   a 之前的值  result2 = {result2}");
        }

另外 Exchange() 也有對(duì)引用類型的重載:

Exchange<T>(T, T)

4,Interlocked.CompareExchange()

其中一個(gè)重載:

public static int CompareExchange (ref int location1, int value, int comparand)

比較兩個(gè) 32 位有符號(hào)整數(shù)是否相等,如果相等,則替換第一個(gè)值。

如果 comparand 和 location1 中的值相等,則將 value 存儲(chǔ)在 location1中。 否則,不會(huì)執(zhí)行任何操作。

看準(zhǔn)了,是 location1 和 comparand 比較!

使用示例如下:

        static void Main(string[] args)
        {
            int location1 = 1;
            int value = 2;
            int comparand = 3;

            Console.WriteLine("運(yùn)行前:");
            Console.WriteLine($" location1 = {location1}    |   value = {value} |   comparand = {comparand}");

            Console.WriteLine("當(dāng) location1 != comparand 時(shí)");
            int result = Interlocked.CompareExchange(ref location1, value, comparand);
            Console.WriteLine($" location1 = {location1} | value = {value} |  comparand = {comparand} |  location1 改變前的值  {result}");

            Console.WriteLine("當(dāng) location1 == comparand 時(shí)");
            comparand = 1;
            result = Interlocked.CompareExchange(ref location1, value, comparand);
            Console.WriteLine($" location1 = {location1} | value = {value} |  comparand = {comparand} |  location1 改變前的值  {result}");
        }

5,Interlocked.Add()

對(duì)兩個(gè) 32 位整數(shù)進(jìn)行求和并用和替換第一個(gè)整數(shù),上述操作作為一個(gè)原子操作完成。

public static int Add (ref int location1, int value);

只能對(duì) int 或 long 有效。

回到第一小節(jié)的多線程求和問題,使用 Interlocked.Add() 來替換Interlocked.Increment()。

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(AddOne);
                thread.Start();
            }

            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("sum = " + sum);
        }
        private static int sum = 0;
        public static void AddOne()
        {
            for (int i = 0; i < 100_0000; i++)
            {
                Interlocked.Add(ref sum,1);
            }
        }

6,Interlocked.Read()

返回一個(gè)以原子操作形式加載的 64 位值。

64位系統(tǒng)上不需要 Read 方法,因?yàn)?4位讀取操作已是原子操作。 在32位系統(tǒng)上,64位讀取操作不是原子操作,除非使用 Read 執(zhí)行。

public static long Read (ref long location);

就是說 32 位系統(tǒng)上才用得上。

到此這篇關(guān)于C#多線程系列之原子操作的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#刪除文件目錄或文件的解決方法

    C#刪除文件目錄或文件的解決方法

    本篇文章是對(duì)C#中如何刪除文件目錄或文件的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C#文字換行的實(shí)現(xiàn)方法

    C#文字換行的實(shí)現(xiàn)方法

    這篇文章主要介紹了C#文字換行的實(shí)現(xiàn)方法,通過自定義函數(shù)實(shí)現(xiàn)針對(duì)特定字符串的換行長(zhǎng)度處理,是比較實(shí)用的技巧,對(duì)于C#的深入學(xué)習(xí)具有一定的借鑒價(jià)值,需要的朋友可以參考下
    2014-12-12
  • 關(guān)于C#中ajax跨域訪問問題

    關(guān)于C#中ajax跨域訪問問題

    最近做項(xiàng)目,需要跨域請(qǐng)求訪問數(shù)據(jù)問題。下面通過本文給大家分享C#中ajax跨域訪問代碼詳解,需要的朋友可以參考下
    2017-05-05
  • opencvsharp瑕疵檢測(cè)的實(shí)現(xiàn)示例

    opencvsharp瑕疵檢測(cè)的實(shí)現(xiàn)示例

    本文主要介紹了opencvsharp瑕疵檢測(cè)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • c#對(duì)xml增刪改查操作示例

    c#對(duì)xml增刪改查操作示例

    本文主要介紹c#對(duì)xml增刪改查操作的示例,大家參考使用吧
    2014-01-01
  • C#使用Task實(shí)現(xiàn)并行編程

    C#使用Task實(shí)現(xiàn)并行編程

    這篇文章介紹了C#使用Task實(shí)現(xiàn)并行編程的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • C#中的checksum計(jì)算公式

    C#中的checksum計(jì)算公式

    這篇文章主要介紹了C#中的checksum計(jì)算公式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • C#實(shí)現(xiàn)CSV文件讀寫的示例詳解

    C#實(shí)現(xiàn)CSV文件讀寫的示例詳解

    這篇文章主要介紹了CsvHelper、TextFieldParser、正則表達(dá)式三種解析CSV文件的方法,順帶也會(huì)介紹一下CSV文件的寫方法,需要的可以參考一下
    2023-05-05
  • C#中ArrayList的使用方法

    C#中ArrayList的使用方法

    這篇文章主要介紹了
    2013-12-12
  • 深入U(xiǎn)nix時(shí)間戳與C# DateTime時(shí)間類型互換的詳解

    深入U(xiǎn)nix時(shí)間戳與C# DateTime時(shí)間類型互換的詳解

    本篇文章是對(duì)Unix時(shí)間戳與C# DateTime時(shí)間類型互換進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06

最新評(píng)論