【CSharp】線程同步

lock語句

lock語句是設置鎖定解除鎖定的一種簡單方式。

開啓20個任務,每個任務對相同的初始值爲0的字段各加一50000次,理論上的結果爲20*50000 = 1000000。但是,多次運行該程序幾乎得不到正確的結果。

方法DoTheJob及其在IL語言中的代碼如下:

public void DoTheJob()
{
    for (int i = 0; i < 50000; i++)
    {
        _sharedState.State += 1;
    }
}
.method public hidebysig instance void  DoTheJob() cil managed
{
	// 代碼大小       46 (0x2e)
	
	// 該方法使用大棧的大小
	.maxstack  3
	
	// 定義局部變量
	// 定義了一個int32類型的局部變量V_0,索引爲0
	// 定義了一個bool 類型的局部變量V_1,索引爲1
	.locals init (int32 V_0, bool V_1)
	
	// 如果修補操作碼,則填充空間。儘管可能消耗處理週期,但未執行任何有意義的操作
	IL_0000:  nop
	
	// 將整數值0作爲int32推送到評估堆棧上
	// 即:i的初始值
	IL_0001:  ldc.i4.0
	
	// 從評估堆棧的頂部彈出當前值並將其存儲到索引爲0的句柄變量量表中
	// 即:i = 0
	IL_0002:  stloc.0
	
	// 無條件地將控制轉移到目標指令(短格式)
	// 即:跳轉執行 i< 50000
	IL_0003:  br.s       IL_0021
	
	// 如果修補操作碼,則填充空間。
	// 儘管可能消耗處理週期,但未執行任何有意義的操作
	IL_0005:  nop
	IL_0006:  nop
	
	// 將索引爲 0 的變量的值加載到評估堆棧上
	IL_0007:  ldarg.0
	
	// 查找對象中其引用當前位於評估堆棧的字段的值
	IL_0008:  ldfld      class SynchronizatonSamples.SharedState SynchronizatonSamples.Job::_sharedState
	
	// 複製評估堆棧上當前最頂端的值,然後將副本推送到評估堆棧上
	IL_000d:  dup
	
	// 對對象調用後期綁定方法,並且將返回值推送到評估堆棧上
	IL_000e:  callvirt   instance int32 SynchronizatonSamples.SharedState::get_State()
	
	// 將整數值 1 作爲 int32 推送到評估堆棧上
	IL_0013:  ldc.i4.1
	
	// 將兩個值相加並將結果推送到評估堆棧上
	IL_0014:  add
	
	// 對對象調用後期綁定方法,並且將返回值推送到評估堆棧上
	IL_0015:  callvirt   instance void SynchronizatonSamples.SharedState::set_State(int32)
	
	// 如果修補操作碼,則填充空間。儘管可能消耗處理週期,但未執行任何有意義的操作
	IL_001a:  nop
	IL_001b:  nop
	IL_001c:  nop
	
	// 將索引 0 處的局部變量加載到評估堆棧上
	IL_001d:  ldloc.0
	
	// 將整數值 1 作爲 int32 推送到評估堆棧上
	IL_001e:  ldc.i4.1
	
	// 將兩個值相加並將結果推送到評估堆棧上
	IL_001f:  add
	
	從評估堆棧的頂部彈出當前值並將其存儲到索引 0 處的局部變量列表中
	IL_0020:  stloc.0
	
	// 將索引 0 處的局部變量加載到評估堆棧上
	IL_0021:  ldloc.0
	
	// 將所提供的 int32 類型的值作爲 int32 推送到評估堆棧上
	IL_0022:  ldc.i4     0xc350
	
	// 比較兩個值。如果第一個值小於第二個值,則將整數值 1 (int32) 推送到評估堆棧上;
	// 反之,將 0 (int32) 推送到評估堆棧上
	// 即:i < 50000
	IL_0027:  clt
	
	// 從評估堆棧的頂部彈出當前值並將其存儲到索引 1 處的局部變量列表中
	IL_0029:  stloc.1
	
	// 將索引 1 處的局部變量加載到評估堆棧上
	IL_002a:  ldloc.1
	
	// 如果 value 爲 true、非空或非零,則將控制轉移到目標指令(短格式)
	IL_002b:  brtrue.s   IL_0005
	
	// 從當前方法返回,並將返回值(如果存在)從調用方的評估堆棧推送到被調用方的評估堆棧上
	IL_002d:  ret
} // end of method Job::DoTheJob

程序先獲取變量_sharedState.State的當前值。執行計算(+1)操作,最後將計算後的值賦值給變量_sharedState.State。

如果有一個線程A執行到(+1)操作,但還未給變量賦值,線程發生了切換。假定在切換的瞬間變量_sharedState.State的值爲100,+1操作後的值爲101,因爲發生了線程切換變量_sharedState.State的值保持100不變,+1操作的值101,進行臨時存儲。新的線程B判斷當前變量_sharedState.State的值爲100,並對其執行新的+1操作,結果變量_sharedState.State的值變爲101。經過多次的線程切換,變量_sharedState.State的值增加爲200。在某一時刻被暫定的線程A又重新獲取時間片,並開始執行。由於之前的+1操作已經完成,僅剩下給變量賦值。繼續執行會將101賦值給變量_sharedState.State。此時,變量的值由原先的200變成了101。

IL語言基本知識

https://docs.microsoft.com/zh-tw/previous-versions/dd229210(v=msdn.10)?redirectedfrom=MSDN

IL語言指令表

https://www.cnblogs.com/yinrq/p/5485630.html

namespace SynchronizatonSamples
{
    public class SharedState
    {
        public int State { get; set; }
    }

    public class Job
    {
        private SharedState _sharedState;
        public Job(SharedState sharedState) => _sharedState = sharedState;

        public void DoTheJob()
        {
            for (int i = 0; i < 50000; i++)
            {
                _sharedState.State += 1;
            }
        }
    }

    class Program
    {
        static void Main()
        {
            // 創建多任務共享的字段
            var state = new SharedState();
            // 定義任務的數量
            var tasks = new System.Threading.Tasks.Task[20];

            for (int i = 0; i < tasks.Length; i++)
                // 雖然使用的是不同的Job實例,但傳入的是相同的字段
                tasks[i] = System.Threading.Tasks.Task.Run(() => new Job(state).DoTheJob());

            // 等待所有任務執行完成
            System.Threading.Tasks.Task.WaitAll(tasks);

            // 輸出多任務的執行結果
            System.Console.WriteLine($"summarized {state.State}");
        }
    }
}

爲了避免上述錯誤,可以通過使用lock關鍵字,對使用共享字段進行加鎖。一個線程獲取到鎖之後,直到其釋放鎖,其他線程才能進行方位,否則只能等待。

namespace SynchronizatonSamples
{
    public class SharedState
    {
        public int State { get; set; }
    }

    public class Job
    {
        private SharedState _sharedState;
        public Job(SharedState sharedState) => _sharedState = sharedState;

        public void DoTheJob()
        {
            for (int i = 0; i < 50000; i++)
            {
                // 多個任務使用相同的字段,可以鎖定所有任務
                lock (_sharedState)
                {
                    _sharedState.State += 1;
                }
            }
        }
    }

    class Program
    {
        static void Main()
        {
            // 創建多任務共享的字段
            var state = new SharedState();
            // 定義任務的數量
            var tasks = new System.Threading.Tasks.Task[20];

            for (int i = 0; i < tasks.Length; i++)
                // 雖然使用的是不同的Job實例,但傳入的是相同的字段
                tasks[i] = System.Threading.Tasks.Task.Run(() => new Job(state).DoTheJob());

            // 等待所有任務執行完成
            System.Threading.Tasks.Task.WaitAll(tasks);

            // 輸出多任務的執行結果
            System.Console.WriteLine($"summarized {state.State}");
        }
    }
}

鎖定靜態成員

可以把鎖放在object類型或靜態成員:

lock (typeof(StaticClass))
{
    
}

lock(this)

一次只有一個線程能訪問相同實例的方法

public class Demo
{
    // Only one thread at a time can access the DoThis and DoThat methods
 
    public void DoThis()
    {
        lock(this)
        {
        }
    }

    public void DoThat()
    {
        lock(this)
        {
        }
    }
}

Interlocked

Interlocked類用於使變量的簡單語句原子化。i++不是線程安全的,它的操作包括從內存中獲取一個值,給該值遞增1,再將它存儲回內存。這些操作都可能會被線程調度器打斷。Interlocked類提供了以線程安全的方式遞增、遞減、交換和讀取值的方法。

與其他同步技術相比,使用Interlocked類會快很多。但是,它只能用於簡單的同步問題

lock(this)
{
    if(_someState == null)
    {
        _someState = newState;
    }
}

=====>>>>
Interlocked.CompareExchange<SomeState>(ref someState, newState, null);



public int State
{
    get
    {
        lock(this)
        {
            return ++_state;
        }
    }
}

=====>>>>
public int State
{
    get
    {
        return Interlocked.Increment(ref _state);
    }
}

Monitor

lock語句由編譯器解析爲使用Monitor類。

lock(obj)
{
    // ...
}

=====>>>>
Monitor.Enter(obj);
try
{
    // ...
}
finally
{
    Monitor.Exit(obj);
}

調用Enter()方法,該方法會一直等待,直到線程鎖定對象爲止。一次只有一個線程能鎖定對象。只要解除了鎖定,線程就可以進入同步階段。Monitor類的Exit方法解除了鎖定。

與lock語句相比,Monitor類的主要優點是:可以添加一個等待被鎖定的超時值。這樣就不會無限期地等待被鎖定,而可以像下面的例子那樣使用TryEnter()方法,其中給它傳遞一個超時值,指定等待被鎖定的最長時間。如果obj被鎖定,TryEmter()方法就把布爾類型的引用參數設置未true。如果另一個線程鎖定obj的事件超過了500毫秒,TryEnter()方法就把變量lockTaken設置爲false,線程不在等待,而是用於其他操作。也許在以後,該線程會嘗試再次獲得鎖定。

bool _lockTaken = false;
Monitor.TryEnter(_obj, 500, ref _lockTaken);
if(_lockTaken)
{
    try
    {
        // ...
    }
    finally
    {
        Monitor.Exit(obj);
    }
}
else
{
    // didn't get the lock, do something else
}

SpinLock

如果基於對象的鎖定(Monitor)的系統開銷由於垃圾回收而過高,可以使用SpinLock結構。如果有大量的鎖定,且鎖定的時間總是非常短,SpinLock結構就很有用。應避免使用多個SpinLock結構,也不要調用任何可能阻塞的內容。

除了體系結構上的區別之外,SpinLock結構的用法非常類似與Monitor類。使用Enter或TryEnter方法獲得鎖定,使用Eixt方法釋放鎖定。SpinLock結構還提供了屬性IsHeld和IsHeldByCurrentThread,指定它當前是否是鎖定的。

WaitHandle

WaitHandle用於等待一個信號。可以等待不同的信號,因爲WaitHandle是一個基類。

namespace AsyncDelegate
{
    public delegate int TakesAWhileDelegate(int x, int ms);
    class Program
    {
        static void Main()
        {
            try
            {
                TakesAWhileDelegate d1 = TakesAWhile;
                System.IAsyncResult ar = d1.BeginInvoke(1, 3000, null, null);
                while (true)
                {
                    System.Console.Write(".");
                    // 等待50ms,如果未等到信號,則停止等待
                    if (ar.AsyncWaitHandle.WaitOne(50))
                    {
                        System.Console.WriteLine("Can get the result now");
                        break;
                    }
                }
                int result = d1.EndInvoke(ar);
                System.Console.WriteLine($"result: {result}");
            }
            catch (System.PlatformNotSupportedException)
            {
                System.Console.WriteLine("PlatformNotSupported exception - with async delegates please use the full .NET Framework");
            }
        }

        public static int TakesAWhile(int x, int ms)
        {
            System.Threading.Tasks.Task.Delay(ms).Wait();
            return 42;
        }
    }
}

可以利用BeginInvoke方法的返回值的AsyncWaitHandle屬性訪問WaitHandle基類。在調用WaitOne方法或者超時發生時,線程會等待接收一個與等待句柄相關的信號。調用EndInvoke方法,線程最終會阻塞,直到得到結果爲止。

  • WaitOne - 等待一個信號、
  • WaitAll   - 等待必須獲得所有信號
  • WaitAny - 等待多個對象中的任意一個

WaitAll與WaitAny時WaitHandle類的靜態方法,接收一個WaitHandle數組。

Mutex

Mutex是跨進程同步訪問的一個類。它非常類似與Monitor類,因爲它們都只有一個線程能擁有鎖定。

在構造函數中,可以指定互斥是否最初應由主線程擁有,定義互斥的名稱,獲得互斥是否已存在的信息。在下面的示例代碼中,第3個參數定義輸入參數 ,接收一個表示互斥是否爲新建的布爾值。如果返回值爲false,就表示互斥已經定義。互斥可以在另一個進程中定義,因爲操作系統能夠識別由名稱的互斥,它由不同的進程共享。如果沒有給互斥指定名稱,互斥就是未命名的,不在不同進程之間共享

var mutex = new Mutex(false, "MutexName", out var createNew);

要打開已有的互斥,還可以使用Mutex.OpenExisting方法。由於Mutex類派生自WaitHandle,因此可以利用WaitOne方法獲得互斥鎖定,在該過程中稱爲該互斥的擁有者。通過調用ReleaseMutex方法釋放互斥。

if(mutex.WaitOne())
{
    try
    {
        // ...
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}
else
{
    // ...
}

由於系統能識別有名稱的互斥 ,因此可以使用它禁止程序啓動兩次。在下面的WPF應用程序中,調用Mutex對象的構造函數。接着驗證指定名稱的互斥是否存在。如果存在,退出應用。

public partial class App : Application
{
    protected override void OnStartup(StartupEvenArgs e)
    {
        var mutex = new Mutex(false, "MutexName", out var mutexCreated);
        if(!mutexCreate)
        {
            MessageBox.Show("You can onlu start one instance of the application");
            Application.Current.Shutdown();
        }
        base.OnStartup(e);
    }
}

Semaphore

信號量非常類似與互斥,其區別是,信號量可以同時由多個線程使用。信號量是一種計數的互斥鎖定。使用信號量,可以定義允許同時訪問受旗語鎖定保護的資源的線程個數。如果需要限制可以訪問可使用資源的線程數,信號量就很有用。例如,如果系統有3個物理端口可用,就允許3個線程同時訪問I/O端口,但第4個線程需要等待前3個線程中的一個釋放資源。

Semaphore類可以命名,使用系統範圍內的資源,允許在不同進程之間同步。

SemaphoreSlim類是簡化版本,只能在一個進程內的不同線程之間同步。

public SemaphoreSlim(int initialCount, int maxCount);

// initialCount - 在程序初始狀態時,釋放出了多少個可以直接被線程使用的資源
// maxCount     - Semaphore所包含的所有資源的數量. 包括當前可用的資源,和當前尚未釋放的資源
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

namespace SemaphoreSample
{
    class Program
    {
        static void Main(string[] args)
        {
            int taskCount = 6;
            int semaphoreCount = 3;
            var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount);
            var tasks = new Task[taskCount];

            for (int i = 0; i < taskCount; i++)
            {
                tasks[i] = Task.Run(() => TaskMain(semaphore));
            }

            Task.WaitAll(tasks);

            WriteLine("All tasks finished");
        }

        private static void TaskMain(SemaphoreSlim semaphore)
        {
            bool isCompleted = false;
            while (!isCompleted)
            {
                if (semaphore.Wait(600))
                {
                    try
                    {
                        WriteLine($"Task {Task.CurrentId} locks the semaphore");
                        Task.Delay(2000).Wait();
                    }
                    finally
                    {
                        WriteLine($"Task {Task.CurrentId} releases the semaphore");
                        semaphore.Release();
                        isCompleted = true;
                    }
                }
                else
                {
                    WriteLine($"Timeout for task {Task.CurrentId}; wait again");
                }
            }
        }
    }
}

Event

事件也是一個系統範圍內的資源同步方法。

  • Wait()   - 等待信號,當前線程處於阻塞狀態
  • Set()     - 發送信號,解除阻塞線程的狀態
  • Reset() - 重置信號,線程再次調用Wait方法,會處於阻塞狀態
  • ManualResetEvent          - 接收到信號後,解除Wait方法的阻塞狀態。直到調用Reset方法,不論執行多少次Wait方法,都不阻塞
  • AutoResetEvent              - 接收到信號後,解除Wait方法的阻塞狀態。在下一次調用Wait方法時,重新處於阻塞狀態 
  • ManualResetEventSlim  - ManualResetEvent 的簡化版本
  • CountdownEvent            - 接收到指定次數,才解除阻塞

對於AutoResetEvent,如果同時有多個線程處於等待狀態。當接收到Set信號時,只有一個線程結束其等待狀態,它不是等待時間最長的線程,而是優先級最高的線程。

namespace EventSample
{
    class Program
    {
        static void Main()
        {
            const int taskCount = 4;

            var cEvent = new System.Threading.CountdownEvent(taskCount);
            var calcs = new Calculator[taskCount];

            for (int i = 0; i < taskCount; i++)
            {
                calcs[i] = new Calculator(cEvent);
                int i1 = i;
                System.Threading.Tasks.Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
            }

            cEvent.Wait();
            System.Console.WriteLine("all finished");

            for (int i = 0; i < taskCount; i++)
            {
                System.Console.WriteLine($"task for {i}, result: {calcs[i].Result}");
            }
        }
    }

    public class Calculator
    {
        private System.Threading.CountdownEvent _cEvent;

        public int Result { get; private set; }

        public Calculator(System.Threading.CountdownEvent ev)
        {
            _cEvent = ev;
        }

        public void Calculation(int x, int y)
        {
            System.Console.WriteLine($"Task {System.Threading.Tasks.Task.CurrentId} starts calculation");
            System.Threading.Tasks.Task.Delay(new System.Random().Next(3000)).Wait();
            Result = x + y;

            // signal the event-completed!
            System.Console.WriteLine($"Task {System.Threading.Tasks.Task.CurrentId} is ready");
            _cEvent.Signal();
        }
    }
}

Barrier

參考

https://blog.csdn.net/wangzhiyu1980/article/details/45688075

Barrier 是 .Net 提供的一直併發的機制,它允許多個任務同步不同階段的併發工作,多任務按階段進行同步。

這裏的關鍵點是【多個任務】和【不同階段】。

假設有4個相同的任務(Task),每個任務都有4個階段(Phase),當他們併發工作時,只有當所有任務的相同步驟都完成時,所有任務纔可以開始下一個步驟。

namespace MyBarrier
{
    class Program
    {
        private static void Phase0Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID}   =====  Phase 0");
        private static void Phase1Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID}   *****  Phase 1");
        private static void Phase2Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID}   ^^^^^  Phase 2");
        private static void Phase3Doing(int TaskID) => System.Console.WriteLine($"Task : #{TaskID}   $$$$$  Phase 3");

        private const int TaskNum = 4;
        private static readonly System.Threading.Tasks.Task[] _Tasks = new System.Threading.Tasks.Task[TaskNum];
        private static System.Threading.Barrier _barrier = new System.Threading.Barrier(TaskNum, (barrier) => System.Console.WriteLine($"-- Current Phase:{_barrier.CurrentPhaseNumber} --"));

        static void Main(string[] args)
        {
            for (int i = 0; i < TaskNum; i++)
            {
                // 創建並啓動四個任務,每個任務各執行四個階段的任務
                _Tasks[i] = System.Threading.Tasks.Task.Factory.StartNew((num) => {
                    var taskid = (int)num;
                    Phase0Doing(taskid); _barrier.SignalAndWait();
                    Phase1Doing(taskid); _barrier.SignalAndWait();
                    Phase2Doing(taskid); _barrier.SignalAndWait();
                    Phase3Doing(taskid); _barrier.SignalAndWait();
                }, i);
            }

            var finalTask = System.Threading.Tasks.Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
            {
                System.Threading.Tasks.Task.WaitAll(_Tasks);
                System.Console.WriteLine("========================================");
                System.Console.WriteLine("All Phase is completed");

                _barrier.Dispose();
            });

            finalTask.Wait();

            System.Console.ReadLine();
        }
    }
}

運行結果:

Task : #1   =====  Phase 0
Task : #0   =====  Phase 0
Task : #3   =====  Phase 0
Task : #2   =====  Phase 0
-- Current Phase:0 --
Task : #0   *****  Phase 1
Task : #3   *****  Phase 1
Task : #2   *****  Phase 1
Task : #1   *****  Phase 1
-- Current Phase:1 --
Task : #3   ^^^^^  Phase 2
Task : #1   ^^^^^  Phase 2
Task : #0   ^^^^^  Phase 2
Task : #2   ^^^^^  Phase 2
-- Current Phase:2 --
Task : #1   $$$$$  Phase 3
Task : #3   $$$$$  Phase 3
Task : #2   $$$$$  Phase 3
Task : #0   $$$$$  Phase 3
-- Current Phase:3 --
========================================
All Phase is completed

根據運行結果可知,在相同的階段內運行的順序不盡相同,但是在每個階段都等到所有任務完成之後纔開始下一個階段的任務。

ReaderWriterLockSlim

爲了使鎖定機制允許鎖定多個讀取器(而不是一個寫入器)訪問某個資源,可以使用ReaderWriterLockSlim類。這個類提供了一個鎖定功能,如果沒有寫入器鎖定資源,就允許多個讀取器訪問資源,但只能有一個寫入器鎖定該資源。

ReaderWriterLockSlim類有阻塞或不阻塞的方法來獲取讀取鎖,如阻塞的EnterReadLock和不阻塞的TryEnterReadLock方法,還可以使用阻塞的EnterWriterLock和不阻塞的TryEnterWriteLock方法獲得 寫入鎖定。如果任務先讀取資源,之後寫如資源,它就可以使用EnterUpgradableReadLock或TryEnterUpgradableReadLock方法獲得可升級的讀取鎖定。有了這個鎖定,就可以獲得寫入鎖定給,而無需釋放讀取鎖定。

下面的示例程序創建了一個包含6項的集合和一個ReaderWriteLockSlim對象。ReaderMethod方法獲得一個讀取鎖定,讀取列表中的所有項,並把它們寫到控制檯。WriteMethod方法試圖獲得一個寫入鎖定,已 改變集合的所有值。在Main方法中,啓動6個任務,已調用ReaderMethod或WriteMethod方法 。

namespace ReaderWriterLockSample
{
    class Program
    {
        private static System.Collections.Generic.List<int> _items = new System.Collections.Generic.List<int>() { 0, 1, 2, 3, 4, 5 };
        private static System.Threading.ReaderWriterLockSlim _rwl = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.SupportsRecursion);

        public static void ReaderMethod(object reader)
        {
            try
            {
                _rwl.EnterReadLock();

                for (int i = 0; i < _items.Count; i++)
                {
                    System.Console.WriteLine($"讀取器({reader})獲得鎖定,輸出集合值。 當前索引:{i}, 當前值:{_items[i]}");
                    System.Threading.Tasks.Task.Delay(40).Wait();
                }
            }
            finally
            {
                _rwl.ExitReadLock();
            }
        }

        public static void WriterMethod(object writer)
        {
            try
            {
                while (!_rwl.TryEnterWriteLock(50))
                {
                    System.Console.WriteLine($"寫入器({writer}):等待獲取寫入鎖定");
                    System.Console.WriteLine($"當前讀取鎖定的數量: {_rwl.CurrentReadCount}");
                }

                System.Console.WriteLine($"寫入器({writer}):獲取到寫入鎖定");
                for (int i = 0; i < _items.Count; i++)
                {
                    _items[i]++;
                    System.Threading.Tasks.Task.Delay(50).Wait();
                }
                System.Console.WriteLine($"寫入器({writer}):寫入完成,即將釋放寫入鎖定");
            }
            finally
            {
                _rwl.ExitWriteLock();
            }
        }

        static void Main()
        {
            System.Console.WriteLine($"程序啓動 ...");
            var taskFactory = new System.Threading.Tasks.TaskFactory(System.Threading.Tasks.TaskCreationOptions.LongRunning, System.Threading.Tasks.TaskContinuationOptions.None);
            var tasks = new System.Threading.Tasks.Task[5];
            tasks[0] = taskFactory.StartNew(WriterMethod, 1); // 寫1
            tasks[1] = taskFactory.StartNew(ReaderMethod, 1); // 讀1
            tasks[2] = taskFactory.StartNew(ReaderMethod, 2); // 讀2
            tasks[3] = taskFactory.StartNew(WriterMethod, 2); // 寫2
            tasks[4] = taskFactory.StartNew(ReaderMethod, 3); // 讀3

            System.Threading.Tasks.Task.WaitAll(tasks);
        }
    }

}

程序運行結果如下:

程序啓動後,三個讀取鎖先後獲得鎖定,開始遍歷集合並輸出。在三個讀取器執行的過程中,寫入鎖定處於等待狀態。當三個讀取操作完成之後,寫入才獲取到鎖定。此時對集合集合的元素+1。同時,可以觀察到寫入1獲取到鎖定之後,寫入2依然處於等待狀態。當寫入1完成之後,寫入2才能獲取到鎖定。

程序啓動 ...
讀取器(3)獲得鎖定,輸出集合值。 當前索引:0, 當前值:0
讀取器(1)獲得鎖定,輸出集合值。 當前索引:0, 當前值:0
讀取器(2)獲得鎖定,輸出集合值。 當前索引:0, 當前值:0
讀取器(2)獲得鎖定,輸出集合值。 當前索引:1, 當前值:1
讀取器(1)獲得鎖定,輸出集合值。 當前索引:1, 當前值:1
讀取器(3)獲得鎖定,輸出集合值。 當前索引:1, 當前值:1
寫入器(1):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
讀取器(2)獲得鎖定,輸出集合值。 當前索引:2, 當前值:2
讀取器(3)獲得鎖定,輸出集合值。 當前索引:2, 當前值:2
讀取器(1)獲得鎖定,輸出集合值。 當前索引:2, 當前值:2
寫入器(1):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
讀取器(1)獲得鎖定,輸出集合值。 當前索引:3, 當前值:3
讀取器(2)獲得鎖定,輸出集合值。 當前索引:3, 當前值:3
讀取器(3)獲得鎖定,輸出集合值。 當前索引:3, 當前值:3
寫入器(1):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
讀取器(1)獲得鎖定,輸出集合值。 當前索引:4, 當前值:4
讀取器(2)獲得鎖定,輸出集合值。 當前索引:4, 當前值:4
讀取器(3)獲得鎖定,輸出集合值。 當前索引:4, 當前值:4
寫入器(1):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
讀取器(3)獲得鎖定,輸出集合值。 當前索引:5, 當前值:5
讀取器(1)獲得鎖定,輸出集合值。 當前索引:5, 當前值:5
讀取器(2)獲得鎖定,輸出集合值。 當前索引:5, 當前值:5
寫入器(1):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 3
寫入器(1):獲取到寫入鎖定
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(1):寫入完成,即將釋放寫入鎖定
寫入器(2):獲取到寫入鎖定
寫入器(2):寫入完成,即將釋放寫入鎖定

再次運行程序:

此時,程序開始運行後,寫入1獲得到鎖定。寫入2與讀取操作均處於等待狀態。寫入1完成操作,釋放鎖定後,寫入2獲得鎖定,讀取操作繼續處於等待狀態。直到寫入2完成,並釋放鎖定之後,讀取操作才獲取到鎖定。

程序啓動 ...
寫入器(1):獲取到寫入鎖定
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(2):等待獲取寫入鎖定
當前讀取鎖定的數量: 0
寫入器(1):寫入完成,即將釋放寫入鎖定
寫入器(2):獲取到寫入鎖定
寫入器(2):寫入完成,即將釋放寫入鎖定
讀取器(2)獲得鎖定,輸出集合值。 當前索引:0, 當前值:2
讀取器(3)獲得鎖定,輸出集合值。 當前索引:0, 當前值:2
讀取器(1)獲得鎖定,輸出集合值。 當前索引:0, 當前值:2
讀取器(1)獲得鎖定,輸出集合值。 當前索引:1, 當前值:3
讀取器(3)獲得鎖定,輸出集合值。 當前索引:1, 當前值:3
讀取器(2)獲得鎖定,輸出集合值。 當前索引:1, 當前值:3
讀取器(2)獲得鎖定,輸出集合值。 當前索引:2, 當前值:4
讀取器(3)獲得鎖定,輸出集合值。 當前索引:2, 當前值:4
讀取器(1)獲得鎖定,輸出集合值。 當前索引:2, 當前值:4
讀取器(1)獲得鎖定,輸出集合值。 當前索引:3, 當前值:5
讀取器(3)獲得鎖定,輸出集合值。 當前索引:3, 當前值:5
讀取器(2)獲得鎖定,輸出集合值。 當前索引:3, 當前值:5
讀取器(2)獲得鎖定,輸出集合值。 當前索引:4, 當前值:6
讀取器(3)獲得鎖定,輸出集合值。 當前索引:4, 當前值:6
讀取器(1)獲得鎖定,輸出集合值。 當前索引:4, 當前值:6
讀取器(2)獲得鎖定,輸出集合值。 當前索引:5, 當前值:7
讀取器(3)獲得鎖定,輸出集合值。 當前索引:5, 當前值:7
讀取器(1)獲得鎖定,輸出集合值。 當前索引:5, 當前值:7

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章