本筆記摘抄自:https://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html,記錄一下學習程序以備后續查用,
一、執行緒同步概述
創建多執行緒來實作讓我們能夠更好地回應應用程式,然而當我們創建了多個執行緒時,就存在多個執行緒同時訪問一個共享資源的情況,此時,我們就需要用到執行緒同步,執行緒同
步可以防止資料(共享資源)的損壞,
一般來說,設計應用程式應盡量避免使用執行緒同步, 因為執行緒同步會產生一些問題:
1.1、它的使用比較繁瑣,我們需要用額外的代碼,把多個執行緒同時訪問的資料包圍起來,并獲取和釋放一個執行緒同步鎖,如果有一個代碼塊忘記獲取鎖,就有可能造成資料損壞,
1.2、使用執行緒同步會影響性能,
1.2.1、獲取和釋放一個鎖是需要時間的,我們在決定哪個執行緒先獲取鎖的時候,CPU要進行協調,這些額外的作業就會對性能造成影響,
1.2.2、執行緒同步一次只允許一個執行緒訪問資源,這樣就會阻塞執行緒,而阻塞執行緒會造成更多的執行緒被創建,這樣CPU就有可能要調度更多的執行緒,從而對性能造成影響,
二、執行緒同步使用
2.1 使用鎖對性能的影響
1.2.1描述過使用鎖會對性能產生影響,下面通過比較使用鎖和不使用鎖消耗的時間來說明這點:
class Program { static void Main(string[] args) { #region 執行緒同步:使用與不使用鎖的耗時對比 int x = 0; //迭代500萬次 const int iterationNumber = 5000000; //不使用鎖 Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < iterationNumber; i++) { x++; } Console.WriteLine("Total time consuming is:{0}ms.", sw.ElapsedMilliseconds); sw.Restart(); //使用鎖 for (int i = 0; i < iterationNumber; i++) { Interlocked.Increment(ref x); } Console.WriteLine("Total time consuming is:{0}ms.", sw.ElapsedMilliseconds); Console.Read(); #endregion } }
運行結果如下:

2.2 Interlocked實作執行緒同步
Interlocked為多個執行緒共享變數提供了原子操作,當我們在多執行緒中對一個整數進行遞增操作時,就需要實作執行緒同步,
下面代碼演示加鎖與不加鎖的區別:
不加鎖:
class Program { //共享資源 public static int number = 0; static void Main(string[] args) { #region 執行緒同步:使用Interlocked實作執行緒同步 //不加鎖 for (int i = 0; i < 10; i++) { Thread thread = new Thread(Add); thread.Start(); } Console.Read(); #endregion } /// <summary> /// 遞增不加鎖 /// </summary> public static void Add() { Thread.Sleep(1000); Console.WriteLine("The current value of number is:{0}", ++number); } }
運行結果如下:

結果與預期可能不太一樣,為了解決這樣的問題,我們可以通過使用 Interlocked.Increment方法來實作自增操作,
實作原理:類似銀行叫號,當有空號且號碼是自己的,才能去辦理相關的業務,否則繼續等待,
加鎖:
class Program { //共享資源 public static int number = 0; public static long signal = 0; static void Main(string[] args) { #region 執行緒同步:使用Interlocked實作執行緒同步 //加鎖 for (int i = 0; i < 10; i++) { Thread thread = new Thread(new ParameterizedThreadStart(AddWithInterlocked)); thread.Start(i); } Console.Read(); #endregion } /// <summary> /// 遞增加Interlocked鎖 /// </summary> public static void AddWithInterlocked(object parameter) { while (Interlocked.Read(ref signal) != 0 || (int)parameter != number) { Thread.Sleep(100); } Interlocked.Increment(ref signal); Console.WriteLine("The current value of number is:{0}", ++number); Interlocked.Decrement(ref signal); } }
運行結果如下:

2.3 Monitor實作執行緒同步
對于上面那個情況,也可以通過Monitor.Enter和Monitor.Exit方法來實作執行緒同步,
C#中通過lock關鍵字來提供簡化的語法(lock可以理解為Monitor.Enter和Monitor.Exit方法的語法糖),
class Program { //共享資源 public static int number = 0; private static readonly object addLock = new object(); static void Main(string[] args) { #region 執行緒同步:使用Monitor實作執行緒同步 //非語法糖 for (int i = 0; i < 10; i++) { Thread thread = new Thread(AddWithMonitor); thread.Start(); } Console.Read(); //語法糖 //for (int i = 0; i < 10; i++) //{ // Thread thread = new Thread(AddWithLock); // thread.Start(); //} //Console.Read(); #endregion } /// <summary> /// 遞增加Monitor鎖 /// </summary> public static void AddWithMonitor() { Thread.Sleep(100); Monitor.Enter(addLock); Console.WriteLine("The current value of number is:{0}", ++number); Monitor.Exit(addLock); } /// <summary> /// 遞增加Lock鎖 /// </summary> public static void AddWithLock() { Thread.Sleep(100); lock (addLock) { Console.WriteLine("The current value of number is:{0}", ++number); } } }
運行結果如下:

接上面的addLock鎖(以下描述為obj鎖),順便學習一下Monitor類的原理:
Monitor在鎖物件obj上會維持兩個執行緒佇列R和W以及一個參考T :
(1)T是對當前獲得了obj鎖的執行緒的參考,
(2) R為就緒佇列,
R佇列上的執行緒,是已經準備好了去競爭獲取obj鎖的執行緒,
執行緒可通過呼叫Monitor.Enter(obj)或Monitor.TryEnter(obj)而直接進入R佇列,可通過呼叫Monitor.Exit(obj)或Monitor.Wait(obj)釋放其所獲得的obj鎖,
當obj鎖被某個執行緒釋放后,這個佇列上的執行緒就會去競爭obj鎖,而獲得obj鎖的執行緒將被T參考,
(3) W為等待佇列,
W佇列上的執行緒,是不會被OS直接調度執行的執行緒,也就是說,等待佇列上的執行緒不能去獲得obj鎖,
執行緒可通過呼叫Monitor.Wait(obj)而直接進入W佇列,可通過呼叫Monitor.Pulse(obj)或Monitor.PulseAll(obj)將W佇列中的第一個等待執行緒或所有等待執行緒移至R佇列,
這時被移至R佇列的這些執行緒就有機會被OS直接調度執行,也就是可以去競爭obj鎖,
(4)Monitor的成員方法,
Monitor.Enter(obj)/Monitor.TryEnter(obj) :執行緒會進入R佇列以等待獲取obj鎖
Monitor.Exit(obj) :執行緒釋放obj鎖(只有獲取了obj鎖的執行緒才能執行Monitor.Exit(obj))
Monitor.Wait(obj): 執行緒釋放當前獲得的obj鎖,然后進入W佇列并阻塞,
Monitor.Pulse(obj) :將W佇列中的第一個等待執行緒移至R佇列中以使第一個執行緒有機會獲取obj鎖,
Monitor.PulseAll(obj):將W佇列中的所有等待執行緒移至R佇列以使得這些執行緒有機會獲得obj鎖,
下面代碼演示Monitor.Wait及Monitor.Pulse的使用:
class Program { //共享資源 private static readonly object addLock = new object(); static void Main(string[] args) { #region 執行緒同步:Monitor.Wait與Monitor.Pulse的使用 for (int i = 0; i < 10; i++) { Thread thread = new Thread(MonitorWaitAndPulse); thread.Start(); } Console.Read(); #endregion } /// <summary> /// Monitor中的Wait與Pulse方法 /// </summary> public static void MonitorWaitAndPulse() { //進入就緒佇列等待獲取鎖資源 Monitor.Enter(addLock); //進來打聲招呼 Console.WriteLine("{0}:我來了,臨時要出去辦一下事,", Thread.CurrentThread.ManagedThreadId); //喚醒等待佇列中的第一個執行緒進入就緒佇列 Monitor.Pulse(addLock); //暫時釋放鎖資源進入等待佇列 Monitor.Wait(addLock); //出去辦事 Thread.Sleep(1000); //回來打聲招呼 Console.WriteLine("{0}:我回來了,", Thread.CurrentThread.ManagedThreadId); //釋放鎖資源 Monitor.Exit(addLock); } }
運行結果如下:

2.4 ReaderWriterLock實作執行緒同步
如果我們需要對一個共享資源執行多次讀取時,用前面所講的類實作的同步鎖都僅允許一個執行緒進行訪問,而其它執行緒將被阻塞,由于只是進行讀取操作,其實是沒有必要
堵塞其他的執行緒, 應該讓它們并發的執行,
此時,可通過ReaderWriterLock類來實作并行讀取,
class Program { //創建物件 public static List<int> lists = new List<int>(); public static ReaderWriterLock readerWriteLock = new ReaderWriterLock(); static void Main(string[] args) { #region 執行緒同步:使用ReaderWriterLock實作執行緒同步 //創建一個執行緒讀取資料 Thread threadWrite = new Thread(Write); threadWrite.Start(); //創建10個執行緒讀取資料 for (int i = 0; i < 10; i++) { Thread threadRead = new Thread(Read); threadRead.Start(); } Console.Read(); #endregion } /// <summary> /// 寫入方法 /// </summary> public static void Write() { //獲取寫入鎖,以10毫秒為超時, readerWriteLock.AcquireWriterLock(10); Random ran = new Random(); int count = ran.Next(1, 10); lists.Add(count); Console.WriteLine("Write the data is:" + count); //釋放寫入鎖 readerWriteLock.ReleaseWriterLock(); } /// <summary> /// 讀取方法 /// </summary> public static void Read() { Thread.Sleep(100); //獲取讀取鎖 readerWriteLock.AcquireReaderLock(10); foreach (int list in lists) { //輸出讀取的資料 Console.WriteLine(list); } // 釋放讀取鎖 readerWriteLock.ReleaseReaderLock(); } }
運行結果如下:

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/94856.html
標籤:C#
上一篇:C#未處理NullReferenceException,未將物件參考設定到物件的實體
下一篇:C# Lazy Loading
