首先,肯定的一點:Microsoft的Framework Class Library(FCL)保證了所有靜態方法都是安全執行緒的。
FCL不保證執行個體方法是安全執行緒的。因為假如全部添加鎖定,會造成效能的巨大損失。另外,假如每個執行個體方法都需要擷取和釋放一個鎖,事實上會造成最終在任何給定的時刻,你的應用程式只有一個線程在運行,這對效能的影響顯而易見。
下面介紹基元線程同步構造。
基元:是指可以在代碼中使用的最簡單的構造。有兩種基元構造:使用者模式(user-mode)和核心模式(kernel-mode)。
使用者模式
使用了特殊的CPU指令來協調線程。
技術:volatile關鍵字、Interlocked類(互鎖)、spinlock(自旋鎖)
常見鎖①:volatile 關鍵字指示一個欄位可以由多個同時執行的線程修改。 聲明為 volatile 的欄位不受編譯器最佳化(假定由單個線程訪問)的限制。 這樣可以確保該欄位在任何時間呈現的都是最新的值。
Interlocked類: 為多個線程共用的變數提供原子操作。。所謂原子操作是指不會被線程調度機制打斷的操作;這種操作一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另一個線程)。
常見鎖②:SpinLock 結構是一個低層級的互斥同步基元,它在等待擷取鎖時進行旋轉。在多核電腦上,當等待時間預計較短且極少出現爭用情況時,SpinLock 的效能將高於其他類型的鎖。即使 SpinLock 未擷取鎖,它也會產生線程的時間片。 它這樣做是為了避免線程優先順序別反轉,並使記憶體回收行程能夠繼續執行。 在使用 SpinLock 時,請確保任何線程持有鎖的時間不會超過一個非常短的時間段,並確保任何線程在持有鎖時不會阻塞。
優點:
應盡量使用基元使用者模式構造,它們的速度要顯著快於核心模式的構造。
協調線程的在硬體中發生的(所以才這麼快)。
但是Microsoft Windows作業系統永遠檢測不到一個線程在基元使用者模式的構造上阻塞了。
由於在使用者模式的基元構造上阻塞的線程池永遠不認為已堵塞,所以線程池不會建立新線程來替換這種臨時的線程。
這些CPU指令只阻塞線程相當短的時間。
缺點:
只有Windows作業系統核心才能停止一個線程的運行(防止它浪費CPU的時間)。
在使用者模式中啟動並執行線程可能被系統搶佔,但線程會以最快的速度再次調度。
想要取得資源但暫時取不到的線程會一直在使用者模式中“自旋”,這可能浪費大量的CPU時間。線程一直在一個CPU上運行,我們稱為“活鎖”(livelock)。
執行個體:
using System;using System.Threading;public class Worker{ // This method is called when the thread is started. public void DoWork() { while (!_shouldStop) { Console.WriteLine("Worker thread: working..."); } Console.WriteLine("Worker thread: terminating gracefully."); } public void RequestStop() { _shouldStop = true; } // Keyword volatile is used as a hint to the compiler that this data // member is accessed by multiple threads. private volatile bool _shouldStop;}public class WorkerThreadExample{ static void Main() { // Create the worker thread object. This does not start the thread. Worker workerObject = new Worker(); Thread workerThread = new Thread(workerObject.DoWork); // Start the worker thread. workerThread.Start(); Console.WriteLine("Main thread: starting worker thread..."); // Loop until the worker thread activates. while (!workerThread.IsAlive) ; // Put the main thread to sleep for 1 millisecond to // allow the worker thread to do some work. Thread.Sleep(1); // Request that the worker thread stop itself. workerObject.RequestStop(); // Use the Thread.Join method to block the current thread // until the object's thread terminates. workerThread.Join(); Console.WriteLine("Main thread: worker thread has terminated."); } // Sample output: // Main thread: starting worker thread... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: terminating gracefully. // Main thread: worker thread has terminated.}
核心模式
由Windows作業系統自身提供的。它們要求在應用程式的線程中調用有作業系統核心實現的函數。
技術:EventWaitHandle(事件)、Semaphore(訊號量)、Mutex(互斥體)
System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.EventWaitHandle System.Threading.Mutex System.Threading.Semaphore
常見鎖③:Mutex 類是 Win32 構造的封裝,它可以跨應用程式定義域邊界進行封送處理,可用於多個等待,並且可用於同步不同進程中的線程。
優點:
線程通過核心模式的構造擷取其他線程擁有的資源時,Windows會阻塞線程以避免它浪費CPU時間。當資源變得可用時,Windows會恢複線程,允許它訪問資源。它不會佔著一個CPU“自旋”。
可實現本機和託管線程相互之間的同步。
可同步在同一台機器的不同進程中啟動並執行線程。
可應用安全性設定,防止未經授權的賬戶訪問它們。
線程可一直阻塞,直到及合作的所有核心模式構造都可用,或者直到集合中的任何核心模式構造可用。
在核心模式的構造上阻塞的線程可指定逾時值:指定時間內訪問不到希望的資源,線程就可以解除阻塞並執行其他任務。
缺點:
將線程從使用者模式切換為核心模式(或者相反)會招致巨大的效能損失,這正是為什麼要避免使用核心構造的原因。另外,線程一直阻塞,會導致“死結“(deadlock)。
死結總是由於活鎖,因為活鎖即浪費CPU時間,有浪費記憶體(線程棧等),而死結只浪費記憶體。
混合構造
兼具上面兩者的長處。在沒有競爭的情況下,快而且不會阻塞(就像使用者模式)。在有競爭的情況,希望它被作業系統核心阻塞。
技術:ManualResetEventSlim類、SemaphoreSlim類、Monitor類、Lock類、ReaderWriterLockSlim類、CountdownEvent類、Barrier類、雙檢鎖.
常見鎖④:Monitor 通常更為可取,因為監視器是專門為 .NET Framework 而設計的,因而它比Mutex可以更好地利用資源。儘管 mutex 比監視器更為強大,但是相對於 Monitor 類,它所需要的互操作轉換更消耗計算資源。
常見鎖⑤:使用 lock (C#) 或 SyncLock (Visual Basic) 關鍵字是Monitor的封裝。通常比直接使用 Monitor 類更可取,一方面是因為 lock 或 SyncLock 更簡潔,另一方面是因為lock 或 SyncLock 確保了即使受保護的代碼引發異常,也可以釋放基礎監視器。
常見鎖⑥:ReaderWriterLock 鎖,在某些情況下,可能希望只在寫入資料時鎖定資源,在不更新資料時允許多個用戶端同時讀取資料。ReaderWriterLock 類線上程修改資源時將強制其獨佔訪問資源,但在讀取資源時則允許非獨佔訪問。 ReaderWriter 鎖可用於代替排它鎖。使用排它鎖時,即使其他線程不需要更新資料,也會讓這些線程等待。
雙檢鎖
常見鎖⑦:雙重檢查鎖定模式(也被稱為”雙重檢查加鎖最佳化”,”鎖暗示”(Lock hint)) 是一種軟體設計模式用來減少並發系統中競爭和同步的開銷。
雙重檢查鎖定模式首先驗證鎖定條件(第一次檢查),只有通過鎖定條件驗證才真正的進行加鎖邏輯並再次驗證條件(第二次檢查)。
它通常用於減少加鎖開銷,尤其是為多線程環境中的單例模式實現“惰性初始化”。惰性初始化的意思是直到第一次訪問時才初始化它的值。
public sealed class Singleton { private static volatile Singleton instance = null; private static object syncRoot = new Object(); private static int count = 100; private Singleton() { } public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } public static Singleton GetSingleton() { if (instance == null) { lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } } return instance; } public override string ToString() { lock (syncRoot) { int buf = --count; Thread.Sleep(20); return buf + "_" + count.ToString(); } } }static void Main(string[] args) { Task<string>[] tasks = new Task<string>[10]; Stopwatch watch = new Stopwatch(); object syncRoot = new Object(); watch.Start(); for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Factory.StartNew<string>(() =>(Singleton.GetSingleton().ToString())); } Task.WaitAll(tasks, 5000);//設定逾時5s watch.Stop(); Console.WriteLine("Tasks running need " + watch.ElapsedMilliseconds + " ms." + "\n"); for (int i = 0; i < tasks.Length; i++) { //逾時處理 if (tasks[i].Status != TaskStatus.RanToCompletion) { Console.WriteLine("Task {0} Error!", i + 1); } else { //save result Console.WriteLine("Tick ID is " + tasks[i].Result); Console.WriteLine(); } } for (int i = 0; i < 3; i++) { Console.WriteLine("Main thread do work!"); Thread.Sleep(200); } Console.ReadKey(); }
輸出:
Tasks running need 298 ms.Tick ID is 96_96Tick ID is 99_99Tick ID is 97_97Tick ID is 98_98Tick ID is 95_95Tick ID is 94_94Tick ID is 93_93Tick ID is 92_92Tick ID is 91_91Tick ID is 90_90Main thread do work!Main thread do work!Main thread do work!
以上就是從0自學C#12--線程同步解決方案匯總以及優缺點的內容,更多相關內容請關注topic.alibabacloud.com(www.php.cn)!