首頁 >後端開發 >C#.Net教程 >從0自學C#12--線程同步解決方法匯總以及優缺點

從0自學C#12--線程同步解決方法匯總以及優缺點

黄舟
黄舟原創
2017-02-04 11:11:151485瀏覽

首先,肯定的一點: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_96

Tick ID is 99_99

Tick ID is 97_97

Tick ID is 98_98

Tick ID is 95_95

Tick ID is 94_94

Tick ID is 93_93

Tick ID is 92_92

Tick ID is 91_91

Tick ID is 90_90

Main thread do work!
Main thread do work!
Main thread do work!

以上就是从0自学C#12--线程同步解决方法汇总以及优缺点的内容,更多相关内容请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn