2019年11月25日 星期一

.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 3 在多執行緒下,使用 lock 關鍵字來做到執行緒安全

.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 3 在多執行緒下,使用 lock 關鍵字來做到執行緒安全

  1. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法
  2. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 2 在多執行緒下,非同步執行加一與減一方法,造成執行緒不安全的現象
  3. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 3 在多執行緒下,使用 lock 關鍵字來做到執行緒安全
  4. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 4 在多執行緒下,使用 Interlocked 來做到執行緒安全
  5. .NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 5 在多執行緒下,不要全部都使用執行緒同步機制 來做到執行緒安全

    從上一篇文章中,.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 2 在多執行緒下,非同步執行加一與減一方法,造成執行緒不安全的現象 中,將把加一與減一這兩個方法,同時在不同的執行緒使用非同步的方式來執行,不過,卻發現到只要當時的系統環境有多可用邏輯處理器可以使用,就會得到不正確的結果與執行速度比起同步執行方法來的慢了些。在設計任何多執行緒程式碼的時候,首要工作就是要能夠確保所設計的程式碼是具備有執行緒安全的效果,也就是對於這裡所提出的範例中,每次執行的結果必須對於 AddSub.counter 這個共用靜態變數於最後執行結果必須為0。
    在這篇文章中,將會使用 C# lock 關鍵字,把需要存取共用變數的關鍵區域 Critical Section 程式碼進行鎖定,讓這個多執行緒的程式具有執行緒安全的特性。
    在這篇文章所提到的專案原始碼,可以從 GitHub 下載

    進行多執行緒的測試,使用 lock 關鍵字

    為了要設計出執行緒安全的程式碼,對於要對共用靜態變數 AddSub.counter 進行加一與減一計算的時候,將使用 lock 關鍵字將其包裝起來,如同底下程式碼
    C Sharp / C#
    class AddSub
    {
        public static int counter = 0;
        public static object locker = new object();
        public void Adds(AddSubAction addSubAction = AddSubAction.NoLock)
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                    lock (locker)
                    {
                        counter++;
                    }
            }
        }
        public void Subs(AddSubAction addSubAction = AddSubAction.NoLock)
        {
            for (int i = 0; i < int.MaxValue; i++)
            {
                    lock (locker)
                    {
                        counter--;
                    }
            }
        }
    }
    
    經過這樣的修正,當執行緒執行到 counter++ 或者 counter-- 程式碼的時候,將會有 lock 進行鎖定保護,也就是說,這裡使用的 核心模式 Kernel Mode 的同步建構子 Synchronization Constructor 來進行鎖定,透過 lock 關鍵字,可以確保同一個時間,對於 lock 內的程式碼僅會有一個執行緒可以執行 lock 內的程式碼。
    對於使用 lock 關鍵字的時候,需要提供一個物件,這裡將會宣告一個靜態變數 public static object locker = new object();
    底下將會使用 C# lock 關鍵字,並請指定不同邏輯處理器數量下的執行結果。
    ThreadSynchronization yes UsingNETLock 10000000
    Counter=0, 88,960ms
    
    ThreadSynchronization yes UsingNETLock 10100000
    Counter=0, 181,327ms
    
    ThreadSynchronization yes UsingNETLock 10101000
    Counter=0, 133,869ms
    
    ThreadSynchronization yes UsingNETLock 11000000
    Counter=0, 106,076ms
    
    ThreadSynchronization yes UsingNETLock 11100000
    Counter=0, 107,196ms
    
    ThreadSynchronization yes UsingNETLock 11110000
    Counter=0, 113,402ms
    
    ThreadSynchronization yes UsingNETLock 11111111
    Counter=0, 121,130ms
    
    從上面的執行結果可以看到,不論指定的邏輯處理器數量是多少,當多執行緒程式執行完畢之後,這個程式的執行結果是正確的;不過,執行這個多執行緒程式所花費的時間真的慘不忍睹,最好的執行時間竟然是僅使用一顆 CPU 的 10000000 的執行結果,需要花費 89 秒的時間,這比起同步程式執行所花費的時間 7.5 秒左右,真的高出太多了,而且,當使用全部八顆邏輯處理器的時候,整體執行完成的時間卻需要高達 121 秒。
    總之,現在已經設計出一個具有執行緒安全的程式碼了,接下來要來嘗試解決執行速度過慢的問題。



    沒有留言:

    張貼留言