91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#原子操作實例分析

發布時間:2022-02-14 09:29:06 來源:億速云 閱讀:142 作者:iii 欄目:開發技術

這篇文章主要講解了“C#原子操作實例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C#原子操作實例分析”吧!

知識點

競爭條件

當兩個或兩個以上的線程訪問共享數據,并且嘗試同時改變它時,就發生爭用的情況。它們所依賴的那部分共享數據,叫做競爭條件。

數據爭用是競爭條件中的一種,出現競爭條件可能會導致內存(數據)損壞或者出現不確定性的行為。

線程同步

如果有 N 個線程都會執行某個操作,當一個線程正在執行這個操作時,其它線程都必須依次等待,這就是線程同步。

多線程環境下出現競爭條件,通常是沒有執行正確的同步而導致的。

CPU時間片和上下文切換

時間片(timeslice)是操作系統分配給每個正在運行的進程微觀上的一段 CPU 時間。

首先,內核會給每個進程分配相等的初始時間片,然后每個進程輪番地執行相應的時間,當所有進程都處于時間 片耗盡的狀態時,內核會重新為每個進程計算并分配時間片,如此往復。

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

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

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

阻塞

阻塞狀態指線程處于等待狀態。當線程處于阻塞狀態時,會盡可能少占用 CPU 時間。

當線程從運行狀態(Runing)變為阻塞狀態時(WaitSleepJoin),操作系統就會將此線程占用的 CPU 時間片分配給別的線程。當線程恢復運行狀態時(Runing),操作系統會重新分配 CPU 時間片。

分配 CPU 時間片時,會出現上下文切換。

內核模式和用戶模式

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

Sleep()Join() 等,都是使用內核模式來阻塞線程,實現線程同步(等待)。

內核模式實現線程等待時,出現上下文切換。這適合等待時間比較長的操作,這樣會減少大量的 CPU 時間損耗。

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

Interlocked 類

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

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

Interlocked 類是靜態類,讓我們先來看看 Interlocked 的常用方法:

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

1,出現問題

問題:

C# 中賦值和一些簡單的數學運算不是原子操作,受多線程環境影響,可能會出現問題。

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

首先我們編寫以下代碼:

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

這個方法的工作完成后,sum 會 +100。

我們在 Main 方法中調用:

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

結果肯定是 5000000,無可爭議的。

但是這樣會慢一些,如果作死,要多線程同時執行呢?

好的,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);
        }

筆者運行一次,出現了 sum = 2633938

我們將每次運算的結果保存到數組中,截取其中一段發現:

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

多個線程使用同一個變量進行操作時,并不知道此變量已經在其它線程中發生改變,導致執行完畢后結果不符合期望。

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

C#原子操作實例分析

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

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

這時 Interlocked 就起作用了,對于一些簡單的操作運算, Interlocked 可以實現原子性的操作。

實現原子性,可以通過多種鎖來解決,目前我們學習到了 lock、Monitor,現在來學習 Interlocked ,后面會學到更加多的鎖的實現。

2,Interlocked.Increment()

用于自增操作。

我們修改一下 AddOne 方法:

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

然后運行,你會發現結果 sum = 5000000 ,這就對了。

說明 Interlocked 可以對簡單值類型進行原子操作。

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

3,Interlocked.Exchange()

Interlocked.Exchange() 實現賦值運算。

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

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

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

測試:

        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不會變化的  b = {b}   |   a 之前的值  result2 = {result2}");
        }

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

Exchange<T>(T, T)

4,Interlocked.CompareExchange()

其中一個重載:

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

比較兩個 32 位有符號整數是否相等,如果相等,則替換第一個值。

如果 comparand 和 location1 中的值相等,則將 value 存儲在 location1中。 否則,不會執行任何操作。

看準了,是 location1 和 comparand 比較!

使用示例如下:

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

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

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

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

5,Interlocked.Add()

對兩個 32 位整數進行求和并用和替換第一個整數,上述操作作為一個原子操作完成。

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

只能對 int 或 long 有效。

回到第一小節的多線程求和問題,使用 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()

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

64位系統上不需要 Read 方法,因為64位讀取操作已是原子操作。 在32位系統上,64位讀取操作不是原子操作,除非使用 Read 執行。

public static long Read (ref long location);

就是說 32 位系統上才用得上。

感謝各位的閱讀,以上就是“C#原子操作實例分析”的內容了,經過本文的學習后,相信大家對C#原子操作實例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

平定县| 珠海市| 民和| 长宁区| 鄯善县| 太仆寺旗| 塘沽区| 二连浩特市| 隆化县| 临泽县| 邓州市| 九龙城区| 岐山县| 梨树县| 新津县| 台前县| 兴海县| 长沙县| 扶风县| 会宁县| 青州市| 黎平县| 木里| 新巴尔虎左旗| 蓬溪县| 海晏县| 马关县| 玉树县| 昆明市| 宜都市| 荔浦县| 文成县| 康乐县| 获嘉县| 兴城市| 榆中县| 阳西县| 阿鲁科尔沁旗| 盘山县| 柯坪县| 佳木斯市|