c# 並行編程、多線程開發中,經常要用到線程鎖,so, 看了許多文章,想總結一下,供自己理解記憶,以及園丁們參考使用,理解的不怎么全面,勿噴!在多線程環境中,多個線程可能會同時訪問同一個資源,為了避免訪問發生沖突,可以根據訪問的復雜程度采取不同的措施,原子操作適用於簡單的單個操作,無鎖算法適用於相對簡單的一連串操作,而線程鎖適用於復雜的一連串操作
1.lock鎖的解釋和用法
官方MSDN的說法:lock 關鍵字可確保當一個線程位於代碼的臨界區時,另一個線程不會進入該臨界區。 如果其他線程嘗試進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。lock 關鍵字在塊的開始處調用 Enter,而在塊的結尾處調用 Exit。 ThreadInterruptedException 引發,如果 Interrupt 中斷等待輸入 lock 語句的線程。通常,應避免鎖定 public 類型,否則實例將超出代碼的控制范圍。
1 private static readonly object objlock = new object(); 2 lock (objlock ) 3 { 4 //要執行的代碼邏輯 5 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace LockTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 TestLock testlock = new TestLock(); 15 Thread th = new Thread(() => 16 { 17 //模擬死鎖:造成死鎖,使lock無法釋放,在i=5時,跳出死循環,釋放lock 18 testlock.DoWorkWithLock(); 19 }); 20 th.Start(); 21 Thread.Sleep(1000); 22 Thread th2 = new Thread(() => 23 { 24 //這個地方你可能會有疑惑,但存在這種情況,比如你封裝的dll,對其它開發人員不是可見的 25 //開發人員很有可能在他的邏輯中,加上一個lock保證方法同時被一個線程調用,但這時有其它的線程正在調用該方法, 26 //但並沒有釋放,死鎖了,那么在這里就不會被執行,除非上面的線程釋放了lock鎖定的對象。這里的lock也可以理解為一個標識,線程1被鎖定的對象 27 //是否已經被釋放, 28 //如果沒有釋放,則無法繼續訪問lock塊中的代碼。 29 lock (testlock) 30 { 31 // 如果該對象中lock(this)不釋放(testlock與this指的是同一個對象),則其它線程如果調用該方法,則會出現直到lock(this)釋放后才能繼續調用。 32 testlock.MotherCallYouDinner(); 33 testlock.DoWorkWithLock(); 34 } 35 }); 36 th2.Start(); 37 Console.Read(); 38 } 39 } 40 41 class TestLock 42 { 43 public static readonly object objLock = new object(); 44 /// <summary> 45 /// 該方法,希望某人在工作的時候,其它人不要打擾(希望只有一個線程在執行) 46 /// </summary> 47 /// <param name="methodIndex"></param> 48 public void DoWorkWithLock() 49 { 50 //鎖當前對象 51 lock (this) 52 { 53 Console.WriteLine("lock this"); 54 int i = 0; 55 while (true) 56 { 57 Console.WriteLine("At work, do not disturb...,Thread id is " + Thread.CurrentThread.ManagedThreadId.ToString()); 58 Thread.Sleep(1000); 59 if (i == 5) 60 { 61 break; 62 } 63 Console.WriteLine(i.ToString()); 64 i++; 65 } 66 } 67 Console.WriteLine("lock dispose"); 68 } 69 public void MotherCallYouDinner() 70 { 71 Console.WriteLine("Your mother call you to home for dinner."); 72 } 73 } 74 }

demo說明:main方法中,創建了一個對象testlock對象,線程1執行該對象的DoWorkWithLock方法,因為死鎖(5s后釋放),造成lock(this)無法釋放,則導致了方法MotherCallYouDinner,DoWorkWithLock在線程2中無法被調用,直到lock(this)釋放,lock(testlock)才能繼續執行,可以這么理解,由於鎖定的同一個對象,線程1釋放了鎖定的對象,其它線程才能訪問。
那么通過lock(static object)方式呢,能不能保證lock塊內的方法,同時只被一個線程執行呢,並且線程2能訪問到MotherCallYouDinner方法。而不像上面出現的那種情況,如果不釋放lock(this),導致線程2都無法執行代碼邏輯。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace LockTest 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 TestLock testlock = new TestLock(); 15 Thread th = new Thread(() => 16 { 17 //模擬死鎖:造成死鎖,使lock無法釋放,在i=5時,跳出死循環,釋放lock 18 testlock.DoWorkWithLock(); 19 }); 20 th.Start(); 21 Thread.Sleep(1000); 22 Thread th2 = new Thread(() => 23 { 24 25 lock (testlock) 26 { 27 testlock.MotherCallYouDinner(); 28 testlock.DoWorkWithLock(); 29 } 30 }); 31 th2.Start(); 32 Console.Read(); 33 } 34 } 35 36 class TestLock 37 { 38 private static readonly object objLock = new object(); 39 /// <summary> 40 /// 該方法,希望某人在工作的時候,其它人不要打擾(希望只有一個線程在執行) 41 /// </summary> 42 /// <param name="methodIndex"></param> 43 public void DoWorkWithLock() 44 { 45 //鎖 46 lock (objLock) 47 { 48 Console.WriteLine("lock this"); 49 int i = 0; 50 while (true) 51 { 52 Console.WriteLine("At work, do not disturb...,Thread id is " + Thread.CurrentThread.ManagedThreadId.ToString()); 53 Thread.Sleep(1000); 54 if (i == 5) 55 { 56 break; 57 } 58 Console.WriteLine(i.ToString()); 59 i++; 60 } 61 } 62 Console.WriteLine("lock dispose"); 63 } 64 public void MotherCallYouDinner() 65 { 66 Console.WriteLine("Your mother call you to home for dinner."); 67 } 68 } 69 }
輸出:

可以看到,將lock(this)更換為鎖定私有的靜態對象,線程2執行了,首先輸出了“Your mother call you to home for dinner.”,同時實現了DoWorkWithLock方法中lock的代碼塊當前只被一個線程執行,直到lcok(objlock)被釋放。因為鎖定的對象,外部不能訪問,線程2不再關心lock(this)是不是已經釋放,都會執行,當然也保證了方法DoWorkWithLock同時被一個線程訪問。
總結:
1、避免使用lock(this),因為無法保證你提供的方法,在外部類中使用的時候,開發人員會不會鎖定當前對象。
通常,應避免鎖定 public 類型,否則實例將超出代碼的控制范圍。常見的結構 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 違反此准則:
如果實例可以被公共訪問,將出現 lock (this) 問題。
如果 MyType 可以被公共訪問,將出現 lock (typeof (MyType)) 問題。
由於進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現 lock(“myLock”) 問題。
最佳做法是定義 private 對象來鎖定, 或 private static 對象變量來保護所有實例所共有的數據。
這里只是說明lock(this)的問題,雖然極端。但開發中,不能保證不會發生。
2、最好使用私有的靜態只讀的鎖對象,保證不會影響其他邏輯的正常執行。
3、盡量避免死鎖的發生。
2.lock自旋鎖、互斥鎖、混合鎖、讀寫鎖
1. 自旋鎖:自旋鎖(Spinlock)是最簡單的線程鎖,基於原子操作實現,它使用一個數值來表示鎖是否已經被獲取,0表示未被獲取,1表示已經獲取,獲取鎖時會先使用原子操作設置數值為1,然后檢查修改前的值是否為0,如果為0則代表獲取成功,否則繼續重試直到成功為止,釋放鎖時會設置數值為0,其他正在獲取鎖的線程會在下一次重試時成功獲取,使用原子操作的原因是,它可以保證多個線程同時把數值0修改到1時,只有一個線程可以觀察到修改前的值為0,其他線程觀察到修改前的值為1
.NET 可以使用以下的類實現自旋鎖:
System.Threading.Thread.SpinWait
System.Threading.SpinWait
System.Threading.SpinLock
使用自旋鎖有個需要注意的問題,自旋鎖保護的代碼應該在非常短的時間內執行完畢,如果代碼長時間運行則其他需要獲取鎖的線程會不斷重試並占用邏輯核心,影響其他線程運行,此外,如果 CPU 只有一個邏輯核心,自旋鎖在獲取失敗時應該立刻調用 Thread.Yield 函數提示操作系統切換到其他線程,因為一個邏輯核心同一時間只能運行一個線程,在切換線程之前其他線程沒有機會運行,也就是切換線程之前自旋鎖沒有機會被釋放
自旋鎖的使用:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var count = 0; 6 var taskList = new Task[10]; 7 Stopwatch sp = new Stopwatch(); 8 sp.Start(); 9 10 // 不要意外復制。每個實例都是獨立的。 11 SpinLock _spinLock = new SpinLock(); 12 for (int i = 0; i < taskList.Length; i++) 13 { 14 taskList[i] = Task.Run(() => 15 { 16 bool _lock = false; 17 for (int j = 0; j < 10_000_000; j++) 18 { 19 _spinLock.Enter(ref _lock); 20 count++; 21 _spinLock.Exit(); 22 _lock = false; 23 } 24 }); 25 } 26 27 sp.Stop(); 28 Task.WaitAll(taskList); 29 Console.WriteLine($"完成! 耗時:{sp.ElapsedTicks}"); 30 Console.WriteLine($"結果:{count}"); 31 } 32 }
自旋鎖的簡單用法
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_4_spinlock 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 15 private static SpinLock m_spinlock; 16 17 private static void Work1(int TaskID) 18 { 19 int i = 0; 20 string log = ""; 21 bool lockToken = false; 22 while (i < RUN_LOOP) 23 { 24 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 25 DateTime.Now.TimeOfDay, TaskID, i); 26 i++; 27 try 28 { 29 lockToken = false; 30 m_spinlock.Enter(ref lockToken); 31 _StrBlder.Append(log); 32 } 33 finally 34 { 35 if (lockToken) 36 m_spinlock.Exit(false); 37 } 38 } 39 } 40 41 private static void Work2(int TaskID) 42 { 43 int i = 0; 44 string log = ""; 45 bool lockToken = false; 46 47 while (i < RUN_LOOP) 48 { 49 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 50 DateTime.Now.TimeOfDay, TaskID, i); 51 i++; 52 try 53 { 54 lockToken = false; 55 m_spinlock.Enter(ref lockToken); 56 _StrBlder.Append(log); 57 } 58 finally 59 { 60 if (lockToken) 61 m_spinlock.Exit(false); 62 } 63 } 64 } 65 66 private static void Work3(int TaskID) 67 { 68 int i = 0; 69 string log = ""; 70 bool lockToken = false; 71 72 while (i < RUN_LOOP) 73 { 74 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 75 DateTime.Now.TimeOfDay, TaskID, i); 76 i++; 77 try 78 { 79 lockToken = false; 80 m_spinlock.Enter(ref lockToken); 81 _StrBlder.Append(log); 82 } 83 finally 84 { 85 if (lockToken) 86 m_spinlock.Exit(false); 87 } 88 } 89 } 90 91 static void Main(string[] args) 92 { 93 _Tasks = new Task[_TaskNum]; 94 _StrBlder = new StringBuilder(); 95 m_spinlock = new SpinLock(); 96 97 98 _Tasks[0] = Task.Factory.StartNew((num) => 99 { 100 var taskid = (int)num; 101 Work1(taskid); 102 }, 0); 103 104 _Tasks[1] = Task.Factory.StartNew((num) => 105 { 106 var taskid = (int)num; 107 Work2(taskid); 108 }, 1); 109 110 _Tasks[2] = Task.Factory.StartNew((num) => 111 { 112 var taskid = (int)num; 113 Work3(taskid); 114 }, 2); 115 116 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 117 { 118 Task.WaitAll(_Tasks); 119 Console.WriteLine("=========================================================="); 120 Console.WriteLine("All Phase is completed"); 121 Console.WriteLine("=========================================================="); 122 Console.WriteLine(_StrBlder); 123 }); 124 125 try 126 { 127 finalTask.Wait(); 128 } 129 catch (AggregateException aex) 130 { 131 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 132 } 133 finally 134 { 135 } 136 Console.ReadLine(); 137 } 138 } 139 }
在每個任務的finally塊中,會調用SpinLock的release,否則在SpinLock.Enter中,程序會在循環中不斷的嘗試獲得鎖,造成死鎖。一旦獲得了鎖,ref 的 LockToken會被變成 true。
超時的用法:SpinLock 同樣也提供了超時機制供開發使用。一個程序示例,worker 1會造成超時,work 2 和work 3則在程序中捕獲超時產生的異常,終止運行。
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_5_spinlock_timeout 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 private static SpinLock m_spinlock; 15 16 17 private static void Work1(int TaskID) 18 { 19 int i = 0; 20 string log = ""; 21 bool lockToken = false; 22 while (i < RUN_LOOP) 23 { 24 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 25 DateTime.Now.TimeOfDay, TaskID, i); 26 i++; 27 try 28 { 29 lockToken = false; 30 m_spinlock.TryEnter(2000, ref lockToken); 31 if (!lockToken) 32 { 33 Console.WriteLine("Work1 TIMEOUT!! Will throw Exception"); 34 throw new TimeoutException("Work1 TIMEOUT!!"); 35 } 36 System.Threading.Thread.Sleep(5000); 37 _StrBlder.Append(log); 38 } 39 finally 40 { 41 if (lockToken) 42 m_spinlock.Exit(false); 43 } 44 } 45 } 46 47 private static void Work2(int TaskID) 48 { 49 int i = 0; 50 string log = ""; 51 bool lockToken = false; 52 53 while (i < RUN_LOOP) 54 { 55 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 56 DateTime.Now.TimeOfDay, TaskID, i); 57 i++; 58 try 59 { 60 lockToken = false; 61 m_spinlock.TryEnter(2000, ref lockToken); 62 if (!lockToken) 63 { 64 Console.WriteLine("Work2 TIMEOUT!! Will throw Exception"); 65 throw new TimeoutException("Work2 TIMEOUT!!"); 66 } 67 68 _StrBlder.Append(log); 69 } 70 finally 71 { 72 if (lockToken) 73 m_spinlock.Exit(false); 74 } 75 } 76 } 77 78 private static void Work3(int TaskID) 79 { 80 int i = 0; 81 string log = ""; 82 bool lockToken = false; 83 84 while (i < RUN_LOOP) 85 { 86 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 87 DateTime.Now.TimeOfDay, TaskID, i); 88 i++; 89 try 90 { 91 lockToken = false; 92 m_spinlock.TryEnter(2000, ref lockToken); 93 if (!lockToken) 94 { 95 Console.WriteLine("Work3 TIMEOUT!! Will throw Exception"); 96 throw new TimeoutException("Work3 TIMEOUT!!"); 97 } 98 _StrBlder.Append(log); 99 } 100 finally 101 { 102 if (lockToken) 103 m_spinlock.Exit(false); 104 } 105 } 106 } 107 108 static void Main(string[] args) 109 { 110 _Tasks = new Task[_TaskNum]; 111 _StrBlder = new StringBuilder(); 112 m_spinlock = new SpinLock(); 113 114 _Tasks[0] = Task.Factory.StartNew((num) => 115 { 116 var taskid = (int)num; 117 Work1(taskid); 118 }, 0); 119 120 _Tasks[1] = Task.Factory.StartNew((num) => 121 { 122 var taskid = (int)num; 123 Work2(taskid); 124 }, 1); 125 126 _Tasks[2] = Task.Factory.StartNew((num) => 127 { 128 var taskid = (int)num; 129 Work3(taskid); 130 }, 2); 131 132 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 133 { 134 Task.WaitAll(_Tasks); 135 Console.WriteLine("=========================================================="); 136 Console.WriteLine("All Phase is completed"); 137 Console.WriteLine("=========================================================="); 138 Console.WriteLine(_StrBlder); 139 }); 140 141 try 142 { 143 finalTask.Wait(); 144 } 145 catch (AggregateException aex) 146 { 147 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 148 } 149 finally 150 { 151 } 152 Console.ReadLine(); 153 } 154 155 } 156 }
操作 SpinWait 的另一種方式 – 實例化 SpinWait。
SpinWait 提供了兩個方法和兩個只讀屬性。
方法:
SpinWait.Reset() : 重置自旋計數器,將計數器置 0。效果就好像沒調用過SpinOnce一樣。
SpinWait.Once() : 執行一次自旋。當SpinWait自旋達到一定次數后,如果有必要當前線程會讓出底層的時間片並觸發上下文切換。
屬性:SpinWait.Count:方法執行單次自旋的次數。
SpinWait.NextSpinWillYield:一個bool值,表示下一次通過SpinOnce方法自旋是否會讓出底層線程的時間片並發生上下文切換。如果需要等待某個條件滿足的時間很短,而且不希望發生上下文切換,基於自旋的【等待】是一種很好的解決方案。
SpinWait : 自旋等待
SpinUntil : 等待某個條件發生
如果發生了長時間的自旋,SpinWait會讓出底層的時間片,並觸發上下文切換。因為長時間的自旋會阻塞優先級更高的線程。當一個線程自旋時,它會將一個內核放入到一個繁忙的循環中,而且它不會讓出處理器時間片的剩余部分。SpinWait的智能邏輯中會在自旋達到足夠長的時間時停止自旋並讓出處理器。當然可以考慮調用Thread.Sleep()方法,它會讓出處理器時間,但開銷比較大。
示例程序:這里通過使用SpinWait 來控制3個Task的執行順序。
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_6_spinwait 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 10; 14 private static bool m_IsWork2Start = false; 15 private static bool m_IsWork3Start = false; 16 17 private static void Work1(int TaskID) 18 { 19 int i = 0; 20 string log = ""; 21 22 while (i < RUN_LOOP) 23 { 24 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 25 DateTime.Now.TimeOfDay, TaskID, i); 26 i++; 27 try 28 { 29 _StrBlder.Append(log); 30 } 31 finally 32 { 33 m_IsWork2Start = true; 34 } 35 } 36 } 37 38 private static void Work2(int TaskID) 39 { 40 int i = 0; 41 string log = ""; 42 43 System.Threading.SpinWait.SpinUntil(() => m_IsWork2Start); 44 45 while ((i < RUN_LOOP) && (m_IsWork2Start)) 46 { 47 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 48 DateTime.Now.TimeOfDay, TaskID, i); 49 i++; 50 try 51 { 52 _StrBlder.Append(log); 53 } 54 finally 55 { 56 m_IsWork3Start = true; 57 } 58 } 59 } 60 61 private static void Work3(int TaskID) 62 { 63 int i = 0; 64 string log = ""; 65 66 System.Threading.SpinWait.SpinUntil(() => m_IsWork3Start); 67 68 while (i < RUN_LOOP) 69 { 70 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 71 DateTime.Now.TimeOfDay, TaskID, i); 72 i++; 73 try 74 { 75 _StrBlder.Append(log); 76 } 77 finally 78 { 79 } 80 } 81 } 82 83 static void Main(string[] args) 84 { 85 _Tasks = new Task[_TaskNum]; 86 _StrBlder = new StringBuilder(); 87 88 _Tasks[0] = Task.Factory.StartNew((num) => 89 { 90 var taskid = (int)num; 91 Work1(taskid); 92 }, 0); 93 94 _Tasks[1] = Task.Factory.StartNew((num) => 95 { 96 var taskid = (int)num; 97 Work2(taskid); 98 }, 1); 99 100 _Tasks[2] = Task.Factory.StartNew((num) => 101 { 102 var taskid = (int)num; 103 Work3(taskid); 104 }, 2); 105 106 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 107 { 108 Task.WaitAll(_Tasks); 109 Console.WriteLine("=========================================================="); 110 Console.WriteLine("All Phase is completed"); 111 Console.WriteLine("=========================================================="); 112 Console.WriteLine(_StrBlder); 113 }); 114 115 try 116 { 117 finalTask.Wait(); 118 } 119 catch (AggregateException aex) 120 { 121 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 122 } 123 finally 124 { 125 } 126 Console.ReadLine(); 127 } 128 } 129 }
總結:
自旋鎖可用於葉級鎖, 在這種情況Monitor下, 通過使用、大小或由於垃圾回收壓力而隱含的對象分配的成本非常高。 旋轉鎖定有助於避免阻塞;但是, 如果你預計會有大量的阻塞, 則可能由於旋轉過多而無法使用自旋鎖。 當鎖的粒度較大且數值較大 (例如, 鏈接列表中的每個節點都有一個鎖) 以及鎖保留時間始終極短時, 旋轉可能非常有利。 通常, 在持有自旋鎖時, 應避免使用以下任何操作:
堵塞
調用自身可能會阻止的任何內容,
同時保留多個自旋鎖,
進行動態調度的調用 (interface 和虛方法),
對任何代碼進行靜態調度調用, 而不是任何代碼, 或
分配內存。
SpinLock僅應在確定這樣做后使用才能改善應用程序的性能。 出於性能方面的考慮, 還SpinLock必須注意, 是值類型。 出於此原因, 必須注意不要意外復制SpinLock實例, 因為兩個實例 (原始和副本) 將完全獨立, 這可能會導致應用程序出現錯誤的行為。 如果必須傳遞實例, 則它應按引用而不是按值傳遞。 SpinLock
不要在只讀SpinLock字段中存儲實例。
2. 互斥鎖:Monitor 和 mutex
定義:private static readonly object Lock = new object();
使用:Monitor.Enter(Lock); //todo Monitor.Exit(Lock);
作用:將會鎖住代碼塊的內容,並阻止其他線程進入該代碼塊,直到該代碼塊運行完成,釋放該鎖。
注意:定義的鎖對象應該是 私有的,靜態的,只讀的,引用類型的對象,這樣可以防止外部改變鎖對象
Monitor有TryEnter的功能,可以防止出現死鎖的問題,lock沒有
定義:private static readonly Mutex mutex = new Mutex();
使用:mutex.WaitOne(); //todo mutex.ReleaseMutex();
作用:將會鎖住代碼塊的內容,並阻止其他線程進入該代碼塊,直到該代碼塊運行完成,釋放該鎖。
注意:定義的鎖對象應該是 私有的,靜態的,只讀的,引用類型的對象,這樣可以防止外部改變鎖對象
Mutex本身是可以系統級別的,所以是可以跨越進程的
Monitor 測試實現:
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_2_monitor_lock 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 15 private static void Work1(int TaskID) 16 { 17 int i = 0; 18 string log = ""; 19 bool lockToken = false; 20 while (i < RUN_LOOP) 21 { 22 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 23 DateTime.Now.TimeOfDay, TaskID, i); 24 i++; 25 try 26 { 27 lockToken = false; 28 Monitor.Enter(_StrBlder, ref lockToken); 29 _StrBlder.Append(log); 30 } 31 finally 32 { 33 if (lockToken) 34 Monitor.Exit(_StrBlder); 35 } 36 } 37 } 38 39 private static void Work2(int TaskID) 40 { 41 int i = 0; 42 string log = ""; 43 bool lockToken = false; 44 45 while (i < RUN_LOOP) 46 { 47 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 48 DateTime.Now.TimeOfDay, TaskID, i); 49 i++; 50 try 51 { 52 lockToken = false; 53 Monitor.Enter(_StrBlder, ref lockToken); 54 _StrBlder.Append(log); 55 } 56 finally 57 { 58 if (lockToken) 59 Monitor.Exit(_StrBlder); 60 } 61 } 62 } 63 64 private static void Work3(int TaskID) 65 { 66 int i = 0; 67 string log = ""; 68 bool lockToken = false; 69 70 while (i < RUN_LOOP) 71 { 72 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 73 DateTime.Now.TimeOfDay, TaskID, i); 74 i++; 75 try 76 { 77 lockToken = false; 78 Monitor.Enter(_StrBlder, ref lockToken); 79 _StrBlder.Append(log); 80 } 81 finally 82 { 83 if (lockToken) 84 Monitor.Exit(_StrBlder); 85 } 86 } 87 } 88 89 static void Main(string[] args) 90 { 91 _Tasks = new Task[_TaskNum]; 92 _StrBlder = new StringBuilder(); 93 94 _Tasks[0] = Task.Factory.StartNew((num) => 95 { 96 var taskid = (int)num; 97 Work1(taskid); 98 }, 0); 99 100 _Tasks[1] = Task.Factory.StartNew((num) => 101 { 102 var taskid = (int)num; 103 Work2(taskid); 104 }, 1); 105 106 _Tasks[2] = Task.Factory.StartNew((num) => 107 { 108 var taskid = (int)num; 109 Work3(taskid); 110 }, 2); 111 112 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 113 { 114 Task.WaitAll(_Tasks); 115 Console.WriteLine("=========================================================="); 116 Console.WriteLine("All Phase is completed"); 117 Console.WriteLine("=========================================================="); 118 Console.WriteLine(_StrBlder); 119 }); 120 121 try 122 { 123 finalTask.Wait(); 124 } 125 catch (AggregateException aex) 126 { 127 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 128 } 129 finally 130 { 131 } 132 Console.ReadLine(); 133 } 134 } 135 }
鎖超時的使用:
其中主要使用的是 Monitor.TryEnter(),函數,其中多了一個設置超時時間的參數。
代碼中讓每個鎖的超時Timer為2秒,在Work1中挺頓5秒,這樣造成了Work2和Work3的超時。
1 using System; 2 using System.Text; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Sample5_3_monitor_lock_timeout 7 { 8 class Program 9 { 10 private static int _TaskNum = 3; 11 private static Task[] _Tasks; 12 private static StringBuilder _StrBlder; 13 private const int RUN_LOOP = 50; 14 15 private static void Work1(int TaskID) 16 { 17 int i = 0; 18 string log = ""; 19 bool lockToken = false; 20 while (i < RUN_LOOP) 21 { 22 log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n", 23 DateTime.Now.TimeOfDay, TaskID, i); 24 i++; 25 try 26 { 27 lockToken = false; 28 Monitor.TryEnter(_StrBlder, 2000, ref lockToken); 29 if (!lockToken) 30 { 31 Console.WriteLine("Work1 TIMEOUT!! Will throw Exception"); 32 throw new TimeoutException("Work1 TIMEOUT!!"); 33 } 34 System.Threading.Thread.Sleep(5000); 35 _StrBlder.Append(log); 36 } 37 finally 38 { 39 if (lockToken) 40 Monitor.Exit(_StrBlder); 41 } 42 } 43 } 44 45 private static void Work2(int TaskID) 46 { 47 int i = 0; 48 string log = ""; 49 bool lockToken = false; 50 51 while (i < RUN_LOOP) 52 { 53 log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n", 54 DateTime.Now.TimeOfDay, TaskID, i); 55 i++; 56 try 57 { 58 lockToken = false; 59 Monitor.TryEnter(_StrBlder, 2000, ref lockToken); 60 if (!lockToken) 61 { 62 Console.WriteLine("Work2 TIMEOUT!! Will throw Exception"); 63 throw new TimeoutException("Work2 TIMEOUT!!"); 64 } 65 66 _StrBlder.Append(log); 67 } 68 finally 69 { 70 if (lockToken) 71 Monitor.Exit(_StrBlder); 72 } 73 } 74 } 75 76 private static void Work3(int TaskID) 77 { 78 int i = 0; 79 string log = ""; 80 bool lockToken = false; 81 82 while (i < RUN_LOOP) 83 { 84 log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n", 85 DateTime.Now.TimeOfDay, TaskID, i); 86 i++; 87 try 88 { 89 lockToken = false; 90 Monitor.TryEnter(_StrBlder, 2000, ref lockToken); 91 if (!lockToken) 92 { 93 Console.WriteLine("Work3 TIMEOUT!! Will throw Exception"); 94 throw new TimeoutException("Work3 TIMEOUT!!"); 95 } 96 _StrBlder.Append(log); 97 } 98 finally 99 { 100 if (lockToken) 101 Monitor.Exit(_StrBlder); 102 } 103 } 104 } 105 106 static void Main(string[] args) 107 { 108 _Tasks = new Task[_TaskNum]; 109 _StrBlder = new StringBuilder(); 110 111 _Tasks[0] = Task.Factory.StartNew((num) => 112 { 113 var taskid = (int)num; 114 Work1(taskid); 115 }, 0); 116 117 _Tasks[1] = Task.Factory.StartNew((num) => 118 { 119 var taskid = (int)num; 120 Work2(taskid); 121 }, 1); 122 123 _Tasks[2] = Task.Factory.StartNew((num) => 124 { 125 var taskid = (int)num; 126 Work3(taskid); 127 }, 2); 128 129 var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => 130 { 131 Task.WaitAll(_Tasks); 132 Console.WriteLine("=========================================================="); 133 Console.WriteLine("All Phase is completed"); 134 Console.WriteLine("=========================================================="); 135 Console.WriteLine(_StrBlder); 136 }); 137 138 try 139 { 140 finalTask.Wait(); 141 } 142 catch (AggregateException aex) 143 { 144 Console.WriteLine("Task failed And Canceled" + aex.ToString()); 145 } 146 finally 147 { 148 } 149 Console.ReadLine(); 150 } 151 } 152 }
mutex 測試實現 :
1、initiallyOwned表示創建mutex的線程是否擁有該互斥體。true表示創建線程擁有互斥鎖,只有在創建線程中調用ReleaseMutex釋放后,其他等待線程才能參與搶奪互斥體的活動。false表示互斥鎖體於與空閑狀態,其他等待互斥鎖的線程立即參與到搶奪互斥鎖的活動中去。
2、在上面程序中如果創建mutex時使用true參數,故在啟動其他線程后必須執行mutex.ReleaseMutex(),如果不釋放mutex,則其他線程將一直等待下去。使用ture,相當於一創建就使用waitone()
3、mutex.WaitOne()與mutex.ReleaseMutex()要像 { } 一樣配對使用,否則將出現 "由於出現被放棄的 mutex,等待過程結束" 的異常
4、mutex與monitor相比,沒有暫時釋放的功能;因此mutex一經釋放,原釋放資源的線程也將重新參與新一輪對mutex的爭奪過程。
1 using (var mutex = new Mutex(false, "name")) 2 { 3 try 4 { 5 mutex.WaitOne(); 6 //do something 7 } 8 catch(Exception ex) 9 { 10 throw ex; 11 } 12 finally 13 { 14 mutex.ReleaseMutex(); 15 } 16 }
不舉栗子了:https://www.cnblogs.com/leo_wl/archive/2012/03/23/2413200.html,,,,https://www.cnblogs.com/guozhiming2003/archive/2008/09/16/1291953.html
3. 混合鎖
混合鎖的特征是在獲取鎖失敗后像自旋鎖一樣重試一定的次數,超過一定次數之后(.NET Core 2.1 是30次)再安排當前進程進入等待狀態
混合鎖的好處是,如果第一次獲取鎖失敗,但其他線程馬上釋放了鎖,當前線程在下一輪重試可以獲取成功,不需要執行毫秒級的線程調度處理;而如果其他線程在短時間內沒有釋放鎖,線程會在超過重試次數之后進入等待狀態,以避免消耗 CPU 資源,因此混合鎖適用於大部分場景
1 internal sealed class SimpleHybridLock : IDisposable 2 { 3 //基元用戶模式構造使用 4 private int m_waiters = 0; 5 6 //基元內核模式構造 7 private AutoResetEvent m_waiterLock = new AutoResetEvent(false); 8 9 public void Enter() 10 { 11 //指出該線程想要獲得鎖 12 if (Equals(Interlocked.Increment(ref m_waiters), 1)) 13 { 14 //無競爭,直接返回 15 return; 16 } 17 18 //另一個線程擁有鎖(發生競爭),使這個線程等待 19 //線程會阻塞,但不會在CPU上“自旋”,從而節省CPU 20 //這里產生較大的性能影響(用戶模式與內核模式之間轉換) 21 //待WaitOne返回后,這個線程拿到鎖 22 m_waiterLock.WaitOne(); 23 } 24 25 public void Leave() 26 { 27 //該線程准備釋放鎖 28 if (Equals(Interlocked.Decrement(ref m_waiters), 0)) 29 { 30 //無線程等待,直接返回 31 return; 32 } 33 34 //有線程等待則喚醒其中一個 35 //這里產生較大的性能影響(用戶模式與內核模式之間轉換) 36 m_waiterLock.Set(); 37 } 38 39 public void Dispose() 40 { 41 m_waiterLock.Dispose(); 42 } 43 }
4.讀寫鎖(ReaderWriterLock )
ReaderWriterLock 定義支持單個寫線程和多個讀線程的鎖。該鎖的作用主要是解決並發讀的性能問題,使用該鎖,可以大大提高數據並發訪問的性能,只有在寫時,才會阻塞所有的讀鎖。
1 using System.Collections.Generic; 2 using System.Windows; 3 using System.Threading; 4 5 6 namespace FYSTest 7 { 8 public partial class MainWindow : Window 9 { 10 List<int> list = new List<int>(); 11 private ReaderWriterLock _rwlock = new ReaderWriterLock(); 12 13 public MainWindow() 14 { 15 InitializeComponent(); 16 Thread ThRead = new Thread(new ThreadStart(Read)); 17 ThRead.IsBackground = true; 18 Thread ThRead2 = new Thread(new ThreadStart(Read)); 19 ThRead2.IsBackground = true; 20 Thread ThWrite = new Thread(new ThreadStart(Write)); 21 ThWrite.IsBackground = true; 22 ThRead.Start(); 23 ThRead2.Start(); 24 ThWrite.Start(); 25 } 26 27 private void Read() 28 { 29 while (true) 30 { 31 //使用一個 System.Int32 超時值獲取讀線程鎖。 32 _rwlock.AcquireReaderLock(100); 33 try 34 { 35 if (list.Count > 0) 36 { 37 int result = list[list.Count - 1]; 38 } 39 } 40 finally 41 { 42 //減少鎖計數,釋放鎖 43 _rwlock.ReleaseReaderLock(); 44 } 45 } 46 } 47 48 int WriteCount = 0;//寫次數 49 private void Write() 50 { 51 while (true) 52 { 53 //使用一個 System.Int32 超時值獲取寫線程鎖。 54 _rwlock.AcquireWriterLock(100); 55 try 56 { 57 list.Add(WriteCount++); 58 } 59 finally 60 { 61 //減少寫線程鎖上的鎖計數,釋放寫鎖 62 _rwlock.ReleaseWriterLock(); 63 } 64 } 65 } 66 } 67 }
讀寫鎖的具體用法栗子:https://blog.csdn.net/zdhlwt2008/article/details/80702605
