多線程使用共用變數,就會涉及到一個線程同步的問題。那如何解決呢? 1) 重構程式,移除多線程的共用變數,讓一個線程只訪問一個自有變數 2) 使用原子操作,一個操作只占用一個量子時間,一次完成,只有噹噹前操作完成之後,其他線程才能進行操作。這樣可以避免使用獨占鎖,避免死鎖。... ...
接上文 多線程編程學習筆記-基礎(一)
接上文 多線程編程學習筆記-基礎(二)
接上文 多線程編程學習筆記-基礎(三)
就如上一篇文章(多線程編程學習筆記-基礎(三))中的示例代碼十,一樣如果多線程使用共用變數,就會涉及到一個線程同步的問題。那如何解決呢?
方法有三:
1) 重構程式,移除多線程的共用變數,讓一個線程只訪問一個自有變數
2) 使用原子操作,一個操作只占用一個量子時間,一次完成,只有噹噹前操作完成之後,其他線程才能進行操作。這樣可以避免使用獨占鎖,避免死鎖。
3) 通過NET構架提供的Mutex、AutoRestEvent、CountDownEven、SpinWait等類,來進行線程間的同步。
一、使用InterLocked類
在上一篇文章中,我們使用lock來解決多線程訪問帶來的問,而在這裡我們使用InterLocked類提供的原子操作,從而幫助我們不需要使用lock鎖來鎖定,以免造成死鎖。
接下來我們改造一下上一篇文章中的代碼,代碼如下。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //引入線程 using System.Diagnostics; namespace ThreadSynchronousDemo { class Program { static void Main(string[] args) { Console.WriteLine("開始,InterLocked 同步"); var c = new Counter(); Thread t = new Thread(() => Count(c)); var t3 = new Thread(() => Count(c)); var t2 = new Thread(() => Count(c)); t.Name = "線程1"; //啟動線程 t.Start(); t2.Name = "線程2"; t2.Start(); t3.Name = "線程3"; t3.Start(); t.Join(); t2.Join(); t3.Join(); Console.WriteLine(string.Format("沒有加鎖的多線程總計:{0}", c.Count)); Console.WriteLine("----------------------------"); var c1 = new CounterInterLocked(); var t4 = new Thread(() => Count(c1)); t4.Name = "線程4"; var t5 = new Thread(() => Count(c1)); t5.Name = "線程5"; var t6 = new Thread(() => Count(c1)); t6.Name = "線程6"; t4.Start(); t5.Start(); t6.Start(); t4.Join(); t5.Join(); t6.Join(); Console.WriteLine(string.Format("InterLocked的多線程總計:{0}", c1.Count)); Console.Read(); } static void Count(CountBase cnt) { for (int i = 0; i < 100000; i++) { cnt.Incerement(); cnt.Dncerement(); } } } abstract class CountBase { public abstract void Incerement(); public abstract void Dncerement(); } class Counter : CountBase { public int Count { get; private set; } public override void Dncerement() { Count--; } public override void Incerement() { Count++; } } class CounterInterLocked : CountBase { private int m_count; public int Count { get { return m_count; } } public override void Dncerement() { Interlocked.Decrement(ref m_count); } public override void Incerement() { Interlocked.Increment(ref m_count); } } }
此程式運行結果如下圖,跟上一篇中的示例十,結果是一樣的。只是代碼上的區別。
二、使用Mutex類
1. 接下來我們來學習使用Mutex類來實現線程間的同步問題。
2. 在程式啟動時,設置InitialOwner為false,這表示如果互斥量已經建立,則允許程式獲取互斥量。如果沒有互斥量,則程式直接運行,等待接收任意鍵,然後釋放互斥量。
3.代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //引入線程 using System.Diagnostics; namespace ThreadSynchronousDemo { class Program { const string mutexName = "syncMutex"; static void Main(string[] args) { Console.WriteLine("開始,Mutex 同步"); using (var mut=new Mutex(false,mutexName)) { if (!mut.WaitOne(TimeSpan.FromSeconds(5),false)) { Console.WriteLine("等待5秒之後運行。。。。"); } else { Console.WriteLine("正在運行。。。。,請輸入任意鍵"); Console.ReadLine(); mut.ReleaseMutex(); Console.WriteLine("釋放互斥量"); } } Console.Read(); } } }
4.運行結果如下圖。
在debug目錄下,先運行主程式,如上圖中1,則程式1正常運行,此時如果再次打開Debug目錄下的應用主程式,則運行結果如上圖中2。說明互斥量起作用了。
5.先在上圖主程式1中輸入k,然後回車,結果如下圖中3。我們從Debug目錄下,再次打開應用程式,則應用程式的運行結果如下圖中4。說明主程式1,已經把互斥量釋放。
註意:具名互斥量是全局的操作系統對象。請務必正確關閉互斥量。最好使用using來包裹互斥量代碼。這種方式可以在不同程式中同步線程。
三、使用SemaphoreSlim類
SemaphoreSlim是Semaphore類的一個輕量級版本。此類限制了同時訪問同一資源的線程數量。
在.net中,類Semaphore封裝了CLR中的內核同步對象。與標準的排他鎖對象(Monitor,Mutex,SpinLock)不同的是,它不是一個排他的鎖對象,它與SemaphoreSlim,ReaderWriteLock等一樣允許多個有限的線程同時訪問共用記憶體資源。
Semaphore就好像一個柵欄,有一定的容量,當裡面的線程數量到達設置的最大值時候,就沒有線程可以進去。然後,如果一個線程工作完成以後出來了,那下一個線程就可以進去了。Semaphore的WaitOne或Release等操作分別將自動地遞減或者遞增信號量的當前計數值。當線程試圖對計數值已經為0的信號量執行WaitOne操作時,線程將阻塞直到計數值大於0。在構造Semaphore時,最少需要2個參數。信號量的初始容量和最大的容量。
1.程式代碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //引入線程 using System.Diagnostics; namespace ThreadSynchronousDemo { class Program { static SemaphoreSlim semapSlim = new SemaphoreSlim(5); static void Main(string[] args) { Console.WriteLine("開始,SemaphoreSlim 同步"); for (int i = 1; i < 9; i++) { string threadName = "線程 " + i; int seconds = new Random().Next(1, 10); var t = new Thread((() => AccessDatabase(threadName, seconds))); t.Start(); } Console.Read(); } static void AccessDatabase(string name,int seconds) { Console.WriteLine("{0} 等待訪問資料庫", name); semapSlim.Wait(); Console.WriteLine("{0} 被授予對資料庫的訪問許可權", name); Thread.Sleep(TimeSpan.FromSeconds(seconds)); Console.WriteLine("{0} 完成了", name); semapSlim.Release(); } } }
2.程式運行結果如下圖。
當程式啟動時,創建了一個SemaphoreSlim對象,併在構造函數中指定了併發的線程數量,然後啟動了10個不同名稱,不同初始運行時間的線程。
每個線程都嘗試獲取資料庫訪問許可權,但是我們使用SemaphoreSlim對象做了限制,只有5個線程能同時訪問資料庫,當前5個線程獲取了資料庫訪問許可權之後,剩下的5個線程只能等待,直到有線程完成工作,並調用Semaphore的Release方法。