在多線程(線程同步)中,咱們將學習多線程中操做共享資源的技術,學習到的知識點以下所示:數據庫
1、執行基本的原子操做安全
在這一小節中,咱們將學習如何在沒有阻塞線程(blocking threads)發生的狀況下,在一個對象上執行基本的原子操做並能阻止競爭條件(race condition)的發生。操做步驟以下所示:多線程
一、使用Visual Studio 2015建立一個新的控制檯應用程序。ide
二、雙擊打開「Program.cs」文件,編寫代碼以下所示:學習
1 using System; 2 using System.Threading; 3 using static System.Console; 4 5 namespace Recipe01 6 { 7 abstract class CounterBase 8 { 9 public abstract void Increment(); 10 11 public abstract void Decrement(); 12 } 13 14 class Counter : CounterBase 15 { 16 private int count; 17 18 public int Count => count; 19 20 public override void Increment() 21 { 22 count++; 23 } 24 25 public override void Decrement() 26 { 27 count--; 28 } 29 } 30 31 class CounterNoLock : CounterBase 32 { 33 private int count; 34 35 public int Count => count; 36 37 public override void Increment() 38 { 39 Interlocked.Increment(ref count); 40 } 41 42 public override void Decrement() 43 { 44 Interlocked.Decrement(ref count); 45 } 46 } 47 48 class Program 49 { 50 static void TestCounter(CounterBase c) 51 { 52 for (int i = 0; i < 100000; i++) 53 { 54 c.Increment(); 55 c.Decrement(); 56 } 57 } 58 59 static void Main(string[] args) 60 { 61 WriteLine("Incorrect counter"); 62 63 var c1 = new Counter(); 64 65 var t1 = new Thread(() => TestCounter(c1)); 66 var t2 = new Thread(() => TestCounter(c1)); 67 var t3 = new Thread(() => TestCounter(c1)); 68 t1.Start(); 69 t2.Start(); 70 t3.Start(); 71 t1.Join(); 72 t2.Join(); 73 t3.Join(); 74 75 WriteLine($"Total count: {c1.Count}"); 76 WriteLine("--------------------------"); 77 78 WriteLine("Correct counter"); 79 80 var c2 = new CounterNoLock(); 81 82 t1 = new Thread(() => TestCounter(c2)); 83 t2 = new Thread(() => TestCounter(c2)); 84 t3 = new Thread(() => TestCounter(c2)); 85 t1.Start(); 86 t2.Start(); 87 t3.Start(); 88 t1.Join(); 89 t2.Join(); 90 t3.Join(); 91 92 WriteLine($"Total count: {c2.Count}"); 93 } 94 } 95 }
三、運行該控制檯應用程序,運行效果(每次運行效果可能不一樣)以下圖所示:spa
在第63行代碼處,咱們建立了一個非線程安全的Counter類的一個對象c1,因爲它是非線程安全的,所以會發生競爭條件(race condition)。線程
在第65~67行代碼處,咱們建立了三個線程來運行c1對象的「TestCounter」方法,在該方法中,咱們按順序對c1對象的count變量執行自增和自減操做。因爲c1不是線程安全的,所以在這種狀況下,咱們獲得的counter值是不肯定的,咱們能夠獲得0值,但多運行幾回,多數狀況下會獲得不是0值得錯誤結果。code
在多線程(基礎篇)中,咱們使用lock關鍵字鎖定對象來解決這個問題,可是使用lock關鍵字會形成其餘線程的阻塞。可是,在本示例中咱們沒有使用lock關鍵字,而是使用了Interlocked構造,它對於基本的數學操做提供了自增(Increment)、自減(Decrement)以及其餘一些方法。對象
2、使用Mutex構造blog
在這一小節中,咱們將學習如何使用Mutex構造同步兩個單獨的程序,即進程間的同步。具體步驟以下所示:
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「Program.cs」文件,編寫代碼以下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 5 namespace Recipe02 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 const string MutexName = "Multithreading"; 12 13 using (var m = new Mutex(false, MutexName)) 14 { 15 // WaitOne方法的做用是阻止當前線程,直到收到其餘實例釋放的處理信號。 16 // 第一個參數是等待超時時間,第二個是否退出上下文同步域。 17 if (!m.WaitOne(TimeSpan.FromSeconds(10), false)) 18 { 19 WriteLine("Second instance is running!"); 20 ReadLine(); 21 } 22 else 23 { 24 WriteLine("Running!"); 25 ReadLine(); 26 // 釋放互斥資源 27 m.ReleaseMutex(); 28 } 29 } 30 31 ReadLine(); 32 } 33 } 34 }
三、編譯代碼,執行兩次該程序,運行效果以下所示:
第一種狀況的運行結果:
第二種狀況的運行結果:
在第11行代碼處,咱們定義了一個mutex(互斥量)的名稱爲「Multithreading」,並在第13行代碼處將其傳遞給了Mutex類的構造方法,該構造方法的第一個參數initialOwner咱們賦值爲false,這容許程序得到一個已經被建立的mutex。若是沒有任何線程鎖定互斥資源,程序只簡單地顯示「Running」,而後等待按下任何鍵以釋放互斥資源。
若是咱們啓動該程序的第二個實例,若是在10秒內咱們沒有在第一個實例下按下任何按鈕以釋放互斥資源,那麼在第二個實例中就會顯示「Second instance is running!」,如第一種狀況的運行結果所示。若是在10內咱們在第一個實例中按下任何鍵以釋放互斥資源,那麼在第二個實例中就會顯示「Running」,如第二種狀況的運行結果所示。
3、使用SemaphoreSlim構造
在這一小節中,咱們將學習如何在SemaphoreSlim構造的幫助下,限制同時訪問資源的線程數量。具體步驟以下所示:
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「Program.cs」文件,編寫代碼以下所示:
1 using System; 2 using System.Threading; 3 using static System.Console; 4 using static System.Threading.Thread; 5 6 namespace Recipe03 7 { 8 class Program 9 { 10 static SemaphoreSlim semaphore = new SemaphoreSlim(4); 11 12 static void AccessDatabase(string name, int seconds) 13 { 14 WriteLine($"{name} waits to access a database"); 15 semaphore.Wait(); 16 WriteLine($"{name} was granted an access to a database"); 17 Sleep(TimeSpan.FromSeconds(seconds)); 18 WriteLine($"{name} is completed"); 19 semaphore.Release(); 20 } 21 22 static void Main(string[] args) 23 { 24 for(int i = 1; i <= 6; i++) 25 { 26 string threadName = "Thread" + i; 27 int secondsToWait = 2 + 2 * i; 28 var t = new Thread(() => AccessDatabase(threadName, secondsToWait)); 29 t.Start(); 30 } 31 } 32 } 33 }
三、運行該控制檯應用程序,運行效果(每次運行效果可能不一樣)以下圖所示:
在第10行代碼處,咱們建立了一個SemaphoreSlim的實例,並對該構造方法傳遞了參數4,該參數指定了能夠有多少個線程同時訪問資源。而後,咱們啓動了6個不一樣名字的線程。每一個線程都試着獲取對數據庫的訪問,可是,咱們限制了最多隻有4個線程能夠訪問數據庫,所以,當4個線程訪問數據庫後,其餘2個線程必須等待,直到其餘線程完成其工做後,調用「Release」方法釋放資源以後才能訪問數據庫。