2019年11月23日 星期六

.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法

.NET C# 單執行緒 同步 多執行緒 非同步 執行緒同步 Synchronization 邏輯處理器數量 設計探討 : Part 1 在單一執行緒下,同步執行加一與減一方法

在這篇文章所提到的專案原始碼,可以從 GitHub 下載

進行單一執行緒的測試

在我這台電腦主機上,使用的是 Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz,其具有四核心與超執行緒 Hyper Threading 的技術,因此,對於作業系統而言,將會看到這台電腦具有八核心的能力。
在這裡,將會進行這樣的計算作業,也就是 加一 +1 與 減一 -1
C Sharp / C#
class AddSub
{
    public static int counter = 0;
    public void Adds()
    {
        for (int i = 0; i < int.MaxValue; i++)
        {
            counter++;
        }
    }
    public void Subs()
    {
        for (int i = 0; i < int.MaxValue; i++)
        {
            counter--;
        }
    }
}
如同上面的程式碼,在此設計了一個類別 AddSub ,該類別提供了兩個執行個體方法與一個靜態變數,第一個物件方法 Adds 會執行一個迴圈,將會跑 int.MaxValue 次,每次回圈內將會執行 counter++ 這個表示式,這裡的 counter 是一個靜態變數,但是 counter 的數值異動卻是由執行個體方法來去變更的;第二個物件方法 Subs 會執行一個迴圈,將會跑 int.MaxValue 次,每次回圈內將會執行 counter-- 這個表示式。也就是說 Adds() 這個方法將會再回圈內每次都進行加一的動作,而對於 Subs() 這個方法將會在回圈內每次都進行減一的動作。
現在,首先要進行的測試目的為將會是要進行僅有一個執行緒的執行結果測試
C Sharp / C#
static void SyncAddSub()
{
    AddSub addSub = new AddSub();
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    addSub.Adds();
    addSub.Subs();
    stopwatch.Stop();
    Console.WriteLine($"Counter={AddSub.counter}, {stopwatch.ElapsedMilliseconds:N0}ms");
}
透過上面的程式碼,設計出 SyncAddSub() 方法,在這個方法內,將會使用 Stopwatch 型別提供計算執行所需要花費時間工具,當計時器開始運作的時候,便會開始同步執行 addSub.Adds(); & addSub.Subs(); 這兩個加一與減一計算工作,最後將會顯示 AddSub.counter 這個靜態變數的值為多少 (當然,正確的結果應該是要為 0) 和所耗費的時間,單位式 ms。
由於大多數的電腦大都具有多CPU或者多核心的能力,也就是說在這台電腦上將會有多個處理器可以使用,如此,便可以採用平行處理的方式,大幅提升整體電腦執行效能。
為了要了解到上面設計的單一執行緒下執行的程式,若在具有不同數量的處理器下來執行,會有甚麼樣的變化,在這裡將會透過 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b10000000 ; 這樣的敘述,來指定這次執行的 .NET 程式,僅能夠限制使用多少顆處理器數量,從這個例子中,經會限制這次執行的程式,僅能夠使用一顆處理器。
為了要方便測試,這裡將會把所有需要測試的步驟,都設計在同一個專案程式碼內,ThreadSynchronization,透過者個專案程式,僅需要在執行的時候,提供不同的引數,就可以做到不同的執行效果,底下是使用方式說明
使用方式 : ThreadSynchronization 多執行緒模式 計算方式 使用CPU模式
多執行緒模式 : Yes , No
計算方式 : NoLock , UserModeLock , UsingNETLock , NoLockByLocal
使用CPU模式 : 1000000 , 11000000 , 10100000 , 11110000 , 10101000
第一個參數將會指定是否要使用多執行緒的方式來執行測試,也就是加一這個方法將會在執行緒 Thread A 中來執行,而減一這個方法將會安排在執行緒 Thread B 中來執行;若在此提供的參數值為 no,則表示加一與減一這兩個方法,都會在同一個執行緒來執行。
第二個參數將會指定要使用哪種 執行緒 同步 Thread Synchronization 機制來設計出執行緒安全 Thread Safe 的程式碼,也就是同樣的程式碼,在多執行緒環境下跑出來的結果,不論執行幾次,執行結果都是可以預期的,也就是結果將會是正確的,沒有模稜兩可問題產生;其中 NoLock 將會不使用任何的執行緒同步機制,當然,將會有可能造成執行緒不安全的問題、UserModeLock 將會使用 User Mode 使用者模式下的執行緒同步機制,這裡將會使用 Interlocked 這個類別來做到、UsingNETLock 這個參數指定使用 C# lock 關鍵字來做到關鍵區域 Critical Section 關鍵區域在同一個時間內,僅會有一個執行緒可以執行這段程式碼、NoLockByLocal 將會在多執行緒執行過程中,當在迴圈跑的時候,不會去變更共用靜態變數,而是在迴圈跑完之後,才會來將累加結果,更新到共用靜態變數內,而在這裡將會透過 Interlocked 這個類別來做到 執行緒安全 的目的。
第三個參數則是用來指定所要使用的處理器 Processor 的數量,要使用這個參數是要確認,當成是於單執行緒 同步執行 或者多執行緒 非同步執行下,若使用不同的處理器數量,是否會有造成甚麼影響;當使用了 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b10000000 ; 這個敘述,表示指定使用該核心內的一個處理器來進行運算,並不是使用到電腦上所有可用的處理器,而當使用了 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b11000000 ; ,則設定使用同一個處理器硬體核心,不過,對於這裡指定兩個處理器,將會採用 Hyper-Threading 技術來運行,根據 Intel 的說明,當採用 Hyper-Threading 技術,可以提升約 20% ~ 30% 的執行效能;若使用了 Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0b10100000 ; 敘述,則是指定了兩個核心處理器作為此次程式要運算的處理器。
現在,請將這篇文章所提到的範例程式,請先使用 Release 建置模式來建置這個專案,接著,下達這樣的指令 ThreadSynchronization no NoLock 10000000
這裡將會先來檢測僅使用單一執行緒來進行加一與減一計算工作,不過,這裡將會指定使用不同的處理器數量,底下是測試結果(在不同的電腦上,因為 CPU 硬體規格不盡相同,所以,可能需要依據讀者本身的可用處理器數量,自行調整第三個參數的內容。
想要確認可用處理器數量,可以打開 Windows 電腦的工作管理員,切換到 [效能] 標籤頁次,在該頁次右下方將會看到 [邏輯處理器] 這個文字標籤的右邊,就是這台電腦上可用的處理器數量,在這台電腦上實際上為單一一顆CPU硬體,但是具有 4 Core 四核心,而且每個核心都有採用 Hyper Threading 的技術,因此,對於整體電腦而言,將會有 8 顆處理器數量,也就是這裡所提到的 邏輯處理器 數量。
底下將會指定在單一執行緒下,同步執行加一與減一方法,並且指定在不同的邏輯處理器數量下的執行結果。
ThreadSynchronization no NoLock 10000000
Counter=0, 7,515ms

ThreadSynchronization no NoLock 10100000
Counter=0, 7,477ms

ThreadSynchronization no NoLock 10101000
Counter=0, 7,458ms

ThreadSynchronization no NoLock 11000000
Counter=0, 7,410ms

ThreadSynchronization no NoLock 11100000
Counter=0, 7,468ms

ThreadSynchronization no NoLock 11110000
Counter=0, 7,497ms

ThreadSynchronization no NoLock 11111111
Counter=0, 7,670ms
從上面的執行結果可以看的出來,若在單一執行緒下,同步執行加一與減一方法,最終的計算結果,也就是 Counter 這個靜態變數的內容將都會是 0,這是正確與可以預期的結果,而且不論使用多少的邏輯處理器數量,所得到的結果都是相當的;這也就是說,若設計的程式採用單一執行緒的同步方式來執行,將不會有執行緒不安全的問題,而且執行上所花費的時間將不會明顯的影響。



沒有留言:

張貼留言