天天看點

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

C# 學習筆記14 再戰多線程Lock

參考

參考:https://blog.csdn.net/weixin_39839541/article/details/111845549?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2aggregatepagefirst_rank_v2~rank_aggregation-1-111845549.pc_agg_rank_aggregation&utm_term=volatile%E8%83%BD%E4%BF%9D%E6%8C%81%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%90%97&spm=1000.2123.3001.4430

參考:https://www.cnblogs.com/liancs/p/3879252.html

參考:https://www.cnblogs.com/apsnet/archive/2012/07/08/2581475.html

參考:https://www.cnblogs.com/soundcode/p/6571167.html

參考:https://www.cnblogs.com/kzwrcom/p/5392141.html

先搞個死鎖先哈哈哈。

死鎖

遞歸死鎖

除了常見的死鎖還有遞歸死鎖。

所謂遞歸函數就是自調用函數,在函數體内直接或間接的調用自己,即函數的嵌套是函數本身。

遞歸方式有兩種:直接遞歸和間接遞歸,直接遞歸就是在函數中出現調用函數本身。間接遞歸,指函數中調用了其他函數,而該其他函數又調用了本函數。例如下方,下面是java代碼,c#不确定會不會死鎖。

public class Test {  
    public void recursive(){  
        this.businessLogic();  
    }  
    public synchronized void businessLogic(){  
        System.out.println("處理業務邏輯");  
    		System.out.println("儲存");
        this.recursive();  
    }  
}  
           

最小粒度的加速,避免死鎖

public class Test {  
    public void recursive(){  
        this.businessLogic();  
    }  
    public  void businessLogic(){  
        System.out.println("處理業務邏輯");  
        this.saveToDB();  
        this.recursive();  
    }  
    public synchronized void saveToDB(){  
        System.out.println("儲存到資料庫");  
    }  
}  

           

死鎖的産生條件

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

死鎖避免方法

加鎖順序

線程2和線程3隻有在擷取了鎖A之後才能嘗試擷取鎖C(譯者注:擷取鎖A是擷取鎖C的必要條件)。因為線程1已經擁有了鎖A,是以線程2和3需要一直等到鎖A被釋放。然後在它們嘗試對B或C加鎖之前,必須成功地對A加了鎖。

按照順序加鎖是一種有效的死鎖預防機制。但是,這種方式需要你事先知道所有可能會用到的鎖(譯者注:并對這些鎖做适當的排序),但總有些時候是無法預知的。

加鎖時限

問題有兩個

1.如何回退(開始前flat=flase,結束的時候判斷下是否完成,否則再調用一次)

2.逾時和重試機制是為了避免在同一時間出現的競争,但是當線程很多時,其中兩個或多個線程的逾時時間一樣或者接近的可能性就會很大,是以就算出現競争而導緻逾時後,由于逾時時間一樣,它們又會同時開始重試,導緻新一輪的競争,帶來了新的問題。

死鎖檢測

每當一個線程獲得了鎖,會線上程和鎖相關的資料結構中(map、graph等等)将其記下。除此之外,每當有線程請求鎖,也需要記錄在這個資料結構中。

線程A請求鎖7,但是鎖7這個時候被線程B持有,這時線程A就可以檢查一下線程B是否已經請求了線程A目前所持有的鎖。如果線程B确實有這樣的請求,那麼就是發生了死鎖(線程A擁有鎖1,請求鎖7;線程B擁有鎖7,請求鎖1)。

可是現實生活這可能是下面這樣。A要繞一大圈,問鎖2,問鎖3,問鎖4,最後才檢測到死鎖的産生路徑。

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

那麼當檢測出死鎖時,這些線程該做些什麼呢?

一個可行的做法是釋放所有鎖,回退,并且等待一段随機的時間後重試。這個和簡單的加鎖逾時類似,不一樣的是隻有死鎖已經發生了才回退,而不會是因為加鎖的請求逾時了。雖然有回退和等待,但是如果有大量的線程競争同一批鎖,它們還是會重複地死鎖(編者注:原因同逾時類似,不能從根本上減輕競争)。

一個更好的方案是給這些線程設定優先級,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖一樣繼續保持着它們需要的鎖。如果賦予這些線程的優先級是固定不變的,同一批線程總是會擁有更高的優先級。為避免這個問題,可以在死鎖發生的時候設定随機的優先級。

這個算法怎麼寫呢(放着先吧)

涉及到圖我就不會了呀,唉。

随便寫一下思路吧。

1.死鎖檢測不能在每個執行線程中檢測。拿一個單獨線程來做?

2.首先寫一個算法,未得鎖出發->已得鎖,如果可以到達。那麼就死鎖了。已得鎖和未得鎖就是資料結構問題了。

3.釋放誰的鎖

volatile

volatile

關鍵字可應用于以下類型的字段:

  • 引用類型。
  • 指針類型(在不安全的上下文中)。 請注意,雖然指針本身可以是可變的,但是它指向的對象不能是可變的。 換句話說,不能聲明“指向可變對象的指針”。
  • 簡單類型,如

    sbyte

    byte

    short

    ushort

    int

    uint

    char

    float

    bool

  • 具有以下基本類型之一的

    enum

    類型:

    byte

    sbyte

    short

    ushort

    int

    uint

  • 已知為引用類型的泛型類型參數。
  • IntPtr 和 UIntPtr。

原理

①操作沒有用volatile來修飾字段時,各個線程都是先從主記憶體(堆)中複制一份資料到自己的工作記憶體中,然後操作自己工作記憶體中的資料,最後再更新到主記憶體中。

②當字段被volatile修飾後,各個線程操作該字段時,都是直接在主記憶體中進行操作的。

因為volatile能夠確定可見性,是以,在一些特定情形下可以使用 volatile 變量替代鎖,例如:在直接修改變量(不需先判斷再修改)的情況下,多個線程同時去修改某個變量,一旦某個線程操作成功了,其他線程對這個變量的修改就立刻建立在最新的變量值上再進行修改,這樣一來就避免了線程安全問題。但是,

但是,要使 volatile 變量提供理想的線程安全,必須同時滿足兩個條件:

①對變量的寫操作不依賴于目前值;

②該變量沒有包含在具有其他變量的不變式中;(不明白)

否則依舊會出現線程安全問題。

package com.springboot.model.mybatisPlus;

/**
 * @author 
 * on 2021/5/3 16:24
 */
class Window implements Runnable {
    private volatile int ticket = 100;

    public void run() {
        for (; ; ) {//通過下面的①②兩個步驟我們可以發現:當不能滿足“對變量的操作不依賴與目前值”,自然就會有線程安全問題。

            if (ticket > 0) {
                try {
                    Thread.sleep(100);//①多個線程同時判斷到“ticket>0”,然後挂起了

                } catch (InterruptedException e) {
                    e.printStackTrace();

                }//②多個線程同時醒來,同時進行“ticket--”操作:

                System.out.println(Thread.currentThread().getName() + ":" + ticket--);

            } else {
                break;

            }

        }

    }
}
public class A03UseVolatileIsNotThreadSafe {
    public static void main(String[]args){
        Window w = new Window();

        Thread t1 = new Thread(w);

        Thread t2 = new Thread(w);

        Thread t3 = new Thread(w);

        t1.setName("視窗1");

        t2.setName("視窗2");

        t3.setName("視窗3");

        t1.start();

        t2.start();

        t3.start();

    }

}


           

lock

lock是一種比較好用的簡單的線程同步方式,它是通過為給定對象擷取互斥鎖來實作同步的。它可以保證當一個線程在關鍵代碼段的時候,另一個線程不會進來,它隻能等待,等到那個線程對象被釋放,也就是說線程出了臨界區。用法:

public void Function() 
{
    object lockThis =new object(); 
    lock(lockThis)
    {
      //Access thread-sensitive resources.
    }
}
           

lock的參數必須是基于引用類型的對象,不要是基本類型像bool,int什麼的,這樣根本不能同步,原因是lock的參數要求是對象,如果傳入int,勢必要發生裝箱操作,這樣每次lock的都将是一個新的不 同的對象。

最好避免使用public類型或不受程式控制的對象執行個體,因為這樣很可能導緻死鎖。

特别是不要使用字元串作為lock的參數,因為字元串被CLR“暫留”,就是說整個應用程式中給定的字元串都隻有一個執行個體,是以更容易造成死鎖現象。

建議使用不被“暫留”的私有或受保護成員作為參數。

其實某些 類已經提供了專門用于被鎖的成員,比如Array類型提供SyncRoot,許多其它集合類型也都提供了SyncRoot。

綜上,使用lock應該注意以下幾點:

1.避免鎖定public類型對象。

//lock(this) 範圍太大,導緻DoNotLockMe無法通路
class C1
    {
        private bool deadlocked = true;
        //這個方法用到了lock,我們希望lock的代碼在同一時刻隻能由一個線程通路
        public void LockMe(object o)
        {
            lock (this)
            {
                while (deadlocked)
                {
                    deadlocked = (bool)o;
                    Console.WriteLine("Foo: I am locked");
                    Thread.Sleep(500);
                }
            }
        }
        //所有線程都可以同時通路的方法
        public void DoNotLockMe()
        {
            Console.WriteLine("I am not locked :)");
        }
    }
           
//lock(syncRoot),DoNotLockMe可以通路
class C1
    {
        private bool deadlocked = true;
        private object syncRoot = new object();
        //這個方法用到了lock,我們希望lock的代碼在同一時刻隻能由一個線程通路
        public void LockMe(object o)
        {
            lock (syncRoot)
            {
                while (deadlocked)
                {
                    deadlocked = (bool)o;
                    Console.WriteLine("Foo: I am locked");
                    Thread.Sleep(500);
                }
            }
        }
        //所有線程都可以同時通路的方法
        public void DoNotLockMe()
        {
            Console.WriteLine("I am not locked :)");
        }
    }
           
class Program
    {
        static void Main(string[] args)
        {
            DeadLock();
        }
        static void DeadLock()
        {
            C1 c1 = new C1();
            //在t1線程中調用LockMe,并将deadlock設為true(将出現死鎖)
            Task t1 = Task.Factory.StartNew(c1.LockMe,true);
            Thread.Sleep(100);
            //在主線程中lock c1
            lock (c1)
            {
                //調用沒有被lock的方法
                c1.DoNotLockMe();
                //調用被lock的方法,并試圖将deadlock解除,可是LockMe被鎖了無法在主線程解鎖
                c1.LockMe(false);
            }
        }
    }
           

2.禁止鎖定類型

這樣範圍就比public更大了,public隻是鎖定一個執行個體,鎖定類型是鎖定所有執行個體

真的需要鎖定所有對象的時候,private static object syncRoot = new object(); 鎖靜态鎖,這樣至少不會鎖沒鎖的方法。

3.禁止鎖定字元串

4.不要鎖定值類型,因為值類型總會裝箱和拆箱。

微軟給出的建議是:隻鎖定私有對象。

首先,類以外的任何代碼都無法鎖定MyClass.somePrivateStaticObject,是以避免了許多死鎖的可能。由于死鎖屬于那種最難找到根源的問題,是以,避免發生死鎖的可能是一件很好的事情。

其次,應用程式中隻有一份MyClass.somePrivateStaticObject的副本,并且系統上運作的其他每個應用程式也隻有一個副本。是以,在同一個應用程式域中的應用程式之間沒有互相影響。

Interlocked

class Atomicity
{
  static int _x, _y;
  static long _z;

  static void Test()
  {
    long myLocal;
    _x = 3;             // 原子的
    _z = 3;             // 32位環境下不是原子的(_z 是64位的)
    myLocal = _z;       // 32位環境下不是原子的(_z 是64位的)
    _y += _x;           // 不是原子的 (結合了讀和寫操作)
    _x++;               // 不是原子的 (結合了讀和寫操作)
  }
}
           

原子操作,無需加鎖。缺點,如果在循環中多次疊代使用

Interlocked

,就可能比在循環外使用一個鎖的效率低(不過

Interlocked

可以實作更高的并發度)。

Interlocked

的數學運算操作僅限于

Increment

Decrement

以及

Add

。如果你希望進行乘法或其它計算,**在無鎖方式下可以使用

CompareExchange

方法(通常與自旋等待一起使用)。

while (initialValue != Interlocked.CompareExchange(ref totalValue, 
            computedValue, initialValue));
           

其他自己看文檔去

Monitor(未研究)

Monitor類提供了與lock類似的功能,不過與lock不同的是,它 能更好的控制同步塊,當調用了Monitor的Enter(Object o)方法時,會擷取o的獨占權,直到調用Exit(Object o)方法時,才會釋放對o的獨占權,可以多次調用Enter(Object o)方法,隻需要調用同樣次數的Exit(Object o)方法即可,Monitor類同時提供了TryEnter(Object o,[int])的一個重載方法,該方法嘗試擷取o對象的獨占權,當擷取獨占權失敗時,将傳回false。

但使用 lock通常比直接使用 Monitor更可取,一方面是因為lock更簡潔,另一方面是因為lock確定了即使受保護的代碼引發異常,也可以釋放基礎螢幕。這是通過finally中調用Exit來實作的。事實上,lock就是用Monitor類來實作的。下面兩段代碼是等效的:

lock(x)
 {
   DoSomething();
 }等效于

object obj =(object)x;
System.Threading.Monitor.Enter(obj);
try
{
 DoSomething();
}
finally
{
 System.Threading.Monitor.Exit(obj);
}
           

關于用法,請參考下面的代碼:

private static objec tm_monitorObject =new object();
[STAThread]
static void Main(string[] args)
 {
 Thread thread =new Thread(new ThreadStart(Do));
 thread.Name ="Thread1";
 Thread thread2 =new Thread(new ThreadStart(Do));
 thread2.Name ="Thread2";
 thread.Start();
 thread2.Start();
 thread.Join();
 thread2.Join();
 Console.Read();
 }
static void Do()
 {
if(!Monitor.TryEnter(m_monitorObject))
 {
 Console.WriteLine("Can't visit Object "+Thread.CurrentThread.Name);
return;
 }
try
{
 Monitor.Enter(m_monitorObject);
 Console.WriteLine( "Enter Monitor "+Thread.CurrentThread.Name);
 Thread.Sleep(5000);
 }
finally
{
 Monitor.Exit(m_monitorObject);
}
}
           

當線程1擷取了m_monitorObject對象獨占權時,線程2嘗試調用TryEnter(m_monitorObject),此時會由于無法擷取獨占權而傳回false,輸出資訊如下:

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

另外,Monitor還提供了三個靜态方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用來實作一種喚醒機制的同步。關于這三個方法的用法,可以參考MSDN,這裡就不詳述了。

Mutex(未研究)

在使用上,Mutex與上述的Monitor比較接近,不過Mutex不具 備Wait,Pulse,PulseAll的功能,是以,我們不能使用Mutex實作類似的喚醒的功能。不過Mutex有一個比較大的特點,Mutex是 跨程序的,是以我們可以在同一台機器甚至遠端的機器上的多個程序上使用同一個互斥體。盡管Mutex也可以實作程序内的線程同步,而且功能也更強大,但這 種情況下,還是推薦使用Monitor,因為Mutex類是win32封裝的,是以它所需要的互操作轉換更耗資源。

ReaderWriterLockSlim

ReaderWriterLock的更新版,預設不支援遞歸調用,所有就某種意義上說避免了遞歸死鎖。性能更好(快百分之10左右)。

定義支援單個寫線程和多個讀線程的鎖。讀寫鎖互斥,寫鎖互斥。

lock鎖對象,這個鎖方法塊,需要搭配try finally。

備注及注意事項

1、對于同一把鎖、多個線程可同時進入讀模式。

2、對于同一把鎖、同時隻允許一個線程進入寫模式。

3、對于同一把鎖、同時隻允許一個線程進入可更新的讀模式。

4、通過預設構造函數建立的讀寫鎖是不支援遞歸的,若想支援遞歸 可通過構造 ReaderWriterLockSlim(LockRecursionPolicy) 建立執行個體。

5、對于同一把鎖、同一線程不可兩次進入同一鎖狀态(開啟遞歸後可以)

6、對于同一把鎖、即便開啟了遞歸、也不可以在進入讀模式後再次進入寫模式或者可更新的讀模式(在這之前必須退出讀模式)。

7、再次強調、不建議啟用遞歸。

8、讀寫鎖具有線程關聯性,即兩個線程間擁有的鎖的狀态互相獨立不受影響、并且不能互相修改其鎖的狀态。

9、更新狀态:在進入可更新的讀模式 EnterUpgradeableReadLock後,可在恰當時間點通過EnterWriteLock進入寫模式。

10、降級狀态:可更新的讀模式可以降級為讀模式:即在進入可更新的讀模式EnterUpgradeableReadLock後, 通過首先調用讀取模式EnterReadLock方法,然後再調用 ExitUpgradeableReadLock 方法。

ReaderWriterLock

// The complete code is located in the ReaderWriterLock class topic.
using System;
using System.Threading;

public class Example
{
    static ReaderWriterLock rwl = new ReaderWriterLock();
    // Define the shared resource protected by the ReaderWriterLock.
    static int resource = 0;

    const int numThreads = 26;
    static bool running = true;

    // Statistics.
    static int readerTimeouts = 0;
    static int writerTimeouts = 0;
    static int reads = 0;
    static int writes = 0;

    public static void Do()
    {
        // Start a series of threads to randomly read from and
        // write to the shared resource.
        Thread[] t = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++)
        {
            t[i] = new Thread(new ThreadStart(ThreadProc));
            t[i].Name = new String(Convert.ToChar(i + 65), 1);
            t[i].Start();
            if (i > 10)
                Thread.Sleep(300);
        }

        // Tell the threads to shut down and wait until they all finish.
        running = false;
        for (int i = 0; i < numThreads; i++)
            t[i].Join();

        // Display statistics.
        Console.WriteLine("\n{0} reads, {1} writes, {2} reader time-outs, {3} writer time-outs.",
              reads, writes, readerTimeouts, writerTimeouts);
        Console.Write("Press ENTER to exit... ");
        Console.ReadLine();
    }

    static void ThreadProc()
    {
        Random rnd = new Random();

        // Randomly select a way for the thread to read and write from the shared
        // resource.
        while (running)
        {
            double action = rnd.NextDouble();
            if (action < .8)
                ReadFromResource(10);
/*            else if (action < .81)
                ReleaseRestore(rnd, 50);*/
            else if (action < .90)
                UpgradeDowngrade(rnd, 100);
            else
                WriteToResource(rnd, 100);
        }
    }

    // Request and release a reader lock, and handle time-outs.
    static void ReadFromResource(int timeOut)
    {
        try
        {
            rwl.AcquireReaderLock(timeOut);
            try
            {
                // It is safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseReaderLock();
            }
        }
        catch (ApplicationException)
        {
            // The reader lock request timed out.
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Request and release the writer lock, and handle time-outs.
    static void WriteToResource(Random rnd, int timeOut)
    {
        try
        {
            rwl.AcquireWriterLock(timeOut);
            try
            {
                //Thread.Sleep(1000);
                // It's safe for this thread to access from the shared resource.
                resource = rnd.Next(500);
                Display("writes resource value " + resource);
                Interlocked.Increment(ref writes);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseWriterLock();
            }
        }
        catch (ApplicationException)
        {
            // The writer lock request timed out.
            Interlocked.Increment(ref writerTimeouts);
        }
    }

    // Requests a reader lock, upgrades the reader lock to the writer
    // lock, and downgrades it to a reader lock again.
    static void UpgradeDowngrade(Random rnd, int timeOut)
    {
        try
        {
            rwl.AcquireReaderLock(timeOut);
            try
            {
                // It's safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);

                // To write to the resource, either release the reader lock and
                // request the writer lock, or upgrade the reader lock. Upgrading
                // the reader lock puts the thread in the write queue, behind any
                // other threads that might be waiting for the writer lock.
                try
                {
                    LockCookie lc = rwl.UpgradeToWriterLock(timeOut);
                    try
                    {
                        // It's safe for this thread to read or write from the shared resource.
                        resource = rnd.Next(500);
                        Display("writes resource value " + resource);
                        Interlocked.Increment(ref writes);
                    }
                    finally
                    {
                        // Ensure that the lock is released.
                        rwl.DowngradeFromWriterLock(ref lc);
                    }
                }
                catch (ApplicationException)
                {
                    // The upgrade request timed out.
                    Interlocked.Increment(ref writerTimeouts);
                }

                // If the lock was downgraded, it's still safe to read from the resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseReaderLock();
            }
        }
        catch (ApplicationException)
        {
            // The reader lock request timed out.
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Release all locks and later restores the lock state.
    // Uses sequence numbers to determine whether another thread has
    // obtained a writer lock since this thread last accessed the resource.
    static void ReleaseRestore(Random rnd, int timeOut)
    {
        int lastWriter;

        try
        {
            rwl.AcquireReaderLock(timeOut);
            try
            {
                // It's safe for this thread to read from the shared resource,
                // so read and cache the resource value.
                int resourceValue = resource;     // Cache the resource value.
                Display("reads resource value " + resourceValue);
                Interlocked.Increment(ref reads);

                // Save the current writer sequence number.
                lastWriter = rwl.WriterSeqNum;

                // Release the lock and save a cookie so the lock can be restored later.
                LockCookie lc = rwl.ReleaseLock();

                // Wait for a random interval and then restore the previous state of the lock.
                Thread.Sleep(rnd.Next(250));
                rwl.RestoreLock(ref lc);

                // Check whether other threads obtained the writer lock in the interval.
                // If not, then the cached value of the resource is still valid.
                if (rwl.AnyWritersSince(lastWriter))
                {
                    resourceValue = resource;
                    Interlocked.Increment(ref reads);
                    Display("resource has changed " + resourceValue);
                }
                else
                {
                    Display("resource has not changed " + resourceValue);
                }
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ReleaseReaderLock();
            }
        }
        catch (ApplicationException)
        {
            // The reader lock request timed out.
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Helper method briefly displays the most recent thread action.
    static void Display(string msg)
    {
        Console.Write("Thread {0} {1}.       \r", Thread.CurrentThread.Name, msg);
    }
}
           

ReaderWriterLockSlim

// The complete code is located in the ReaderWriterLock class topic.
using System;
using System.Threading;

public class Example3
{
    static ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
    // Define the shared resource protected by the ReaderWriterLock.
    static int resource = 0;

    const int numThreads = 26;
    static bool running = true;

    // Statistics.
    static int readerTimeouts = 0;
    static int writerTimeouts = 0;
    static int reads = 0;
    static int writes = 0;

    public static void Do()
    {
        // Start a series of threads to randomly read from and
        // write to the shared resource.
        Thread[] t = new Thread[numThreads];
        for (int i = 0; i < numThreads; i++)
        {
            t[i] = new Thread(new ThreadStart(ThreadProc));
            t[i].Name = new String(Convert.ToChar(i + 65), 1);
            t[i].Start();
            if (i > 10)
                Thread.Sleep(300);
        }

        // Tell the threads to shut down and wait until they all finish.
        running = false;
        for (int i = 0; i < numThreads; i++)
            t[i].Join();

        // Display statistics.
        Console.WriteLine("\n{0} reads, {1} writes, {2} reader time-outs, {3} writer time-outs.",
              reads, writes, readerTimeouts, writerTimeouts);
        Console.Write("Press ENTER to exit... ");
        Console.ReadLine();
    }

    static void ThreadProc()
    {
        Random rnd = new Random();

        // Randomly select a way for the thread to read and write from the shared
        // resource.
        while (running)
        {
            double action = rnd.NextDouble();
            if (action < .8)
                ReadFromResource(10);
            else if (action < .90)
                UpgradeDowngrade(rnd, 100);
            else
                WriteToResource(rnd, 100);
        }
    }

    // Request and release a reader lock, and handle time-outs.
    static void ReadFromResource(int timeOut)
    {

        if (rwl.TryEnterReadLock(timeOut))
        {
            try
            {
                // It is safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ExitReadLock();
            }
        }
        else
        {
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Request and release the writer lock, and handle time-outs.
    static void WriteToResource(Random rnd, int timeOut)
    {

        if (rwl.TryEnterWriteLock(timeOut))
        {
            try
            {
                //Thread.Sleep(1000);
                // It's safe for this thread to access from the shared resource.
                resource = rnd.Next(500);
                Display("writes resource value " + resource);
                Interlocked.Increment(ref writes);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ExitWriteLock();
            }
        }
        else
        {
            Interlocked.Increment(ref writerTimeouts);
        }
    }

    // Requests a reader lock, upgrades the reader lock to the writer
    // lock, and downgrades it to a reader lock again.
    static void UpgradeDowngrade(Random rnd, int timeOut)
    {

        if (rwl.TryEnterUpgradeableReadLock(timeOut))
        {
            try
            {
                // It's safe for this thread to read from the shared resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);

                // To write to the resource, either release the reader lock and
                // request the writer lock, or upgrade the reader lock. Upgrading
                // the reader lock puts the thread in the write queue, behind any
                // other threads that might be waiting for the writer lock.
                if (rwl.TryEnterWriteLock(timeOut)) {
                    try
                    {
                        // It's safe for this thread to read or write from the shared resource.
                        resource = rnd.Next(500);
                        Display("writes resource value " + resource);
                        Interlocked.Increment(ref writes);
                    }
                    finally
                    {
                        // Ensure that the lock is released.
                        rwl.ExitWriteLock();
                    }
                }
                else
                {
                    // The upgrade request timed out.
                    Interlocked.Increment(ref writerTimeouts);
                }

                // If the lock was downgraded, it's still safe to read from the resource.
                Display("reads resource value " + resource);
                Interlocked.Increment(ref reads);
            }
            finally
            {
                // Ensure that the lock is released.
                rwl.ExitUpgradeableReadLock();
            }

        }
        else
        {
            Interlocked.Increment(ref readerTimeouts);
        }
    }

    // Release all locks and later restores the lock state.
    // Uses sequence numbers to determine whether another thread has
    // obtained a writer lock since this thread last accessed the resource.

    // Helper method briefly displays the most recent thread action.
    static void Display(string msg)
    {
        Console.Write("Thread {0} {1}.       \r", Thread.CurrentThread.Name, msg);
    }
}
           

SynchronizationAttribute(未研究)

當我們确定某個類的執行個體在同一時刻隻能被一個線程通路時,我們可以直接将類辨別成Synchronization的,這樣,CLR會自動對這個類實施同步機制,實際上,這裡面涉及到同步域的概念,當類按如下設計時,我們可以確定類的執行個體無法被多個線程同時通路

  1). 在類的聲明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute屬性。

2). 繼承至System.ContextBoundObject

需要注意的是,要實作上述機制,類必須繼承至System.ContextBoundObject,換句話說,類必須是上下文綁定的。

一個示範類代碼如下:

[System.Runtime.Remoting.Contexts.Synchronization]
public class SynchronizedClass : System.ContextBoundObject
 {

 }
           

MethodImplAttribute(未研究)

如果臨界區是跨越整個方法的,也就是說,整個方法内部的代碼都需要上鎖的 話,使用MethodImplAttribute屬性會更簡單一些。這樣就不用在方法内部加鎖了,隻需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空間System.Runtime.CompilerServices裡面。但要注意這個屬性會使整個方法加鎖,直到方法傳回,才釋放鎖。是以,使用上不太靈活。

各種鎖概念

自旋鎖

在許多場景中,同步資源的鎖定時間很短,為了這一小段時間去切換線程,線程挂起和恢複現場的花費可能會讓系統得不償失。如果實體機器有多個處理器,能夠讓兩個或以上的線程同時并行執行,我們就可以讓後面那個請求鎖的線程不放棄CPU的執行時間,看看持有鎖的線程是否很快就會釋放鎖。

而為了讓目前線程“稍等一下”,我們需讓目前線程進行自旋,如果在自旋完成後前面鎖定同步資源的線程已經釋放了鎖,那麼目前線程就可以不必阻塞而是直接擷取同步資源,進而避免切換線程的開銷。這就是自旋鎖。

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

自旋等待雖然避免了線程切換的開銷,但它要占用處理器時間。如果鎖被占用的時間很短,自旋等待的效果就會非常好。反之,如果鎖被占用的時間很長,那麼自旋的線程隻會白浪費處理器資源。是以,自旋等待的時間必須要有一定的限度,如果自旋超過了限定次數(預設是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應當挂起線程。

适應性自旋鎖

自适應意味着自旋的時間(次數)不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀态來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運作中,那麼虛拟機就會認為這次自旋也是很有可能再次成功,進而它将允許自旋等待持續相對更長的時間。如果對于某個鎖,自旋很少成功獲得過,那在以後嘗試擷取這個鎖時将可能省略掉自旋過程,直接阻塞線程,避免浪費處理器資源。

公平鎖和非公平鎖

公平鎖是指多個線程按照申請鎖的順序來擷取鎖,線程直接進入隊列中排隊,隊列中的第一個線程才能獲得鎖。公平鎖的優點是等待鎖的線程不會餓死。缺點是整體吞吐效率相對非公平鎖要低,等待隊列中除第一個線程以外的所有線程都會阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大。

非公平鎖是多個線程加鎖時直接嘗試擷取鎖,擷取不到才會到等待隊列的隊尾等待。但如果此時鎖剛好可用,那麼這個線程可以無需阻塞直接擷取到鎖,是以非公平鎖有可能出現後申請鎖的線程先擷取鎖的場景。非公平鎖的優點是可以減少喚起線程的開銷,整體的吞吐效率高,因為線程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線程。缺點是處于等待隊列中的線程可能會餓死,或者等很久才會獲得鎖。

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

如上圖所示,假設有一口水井,有管理者看守,管理者有一把鎖,隻有拿到鎖的人才能夠打水,打完水要把鎖還給管理者。每個過來打水的人都要管理者的允許并拿到鎖之後才能去打水,如果前面有人正在打水,那麼這個想要打水的人就必須排隊。管理者會檢視下一個要去打水的人是不是隊伍裡排最前面的人,如果是的話,才會給你鎖讓你去打水;如果你不是排第一的人,就必須去隊尾排隊,這就是公平鎖。

但是對于非公平鎖,管理者對打水的人沒有要求。即使等待隊伍裡有排隊等待的人,但如果在上一個人剛打完水把鎖還給管理者而且管理者還沒有允許等待隊伍裡下一個人去打水時,剛好來了一個插隊的人,這個插隊的人是可以直接從管理者那裡拿到鎖去打水,不需要排隊,原本排隊等待的人隻能繼續等待。如下圖所示:

C# 學習筆記14 再戰多線程Lock參考死鎖volatilelockInterlockedMonitor(未研究)Mutex(未研究)ReaderWriterLockSlimSynchronizationAttribute(未研究)MethodImplAttribute(未研究)各種鎖概念下一章線程通信預告

可重入鎖

可重入鎖又名遞歸鎖,是指在同一個線程在外層方法擷取鎖的時候,再進入該線程的内層方法會自動擷取鎖(前提鎖對象得是同一個對象或者class),不會因為之前已經擷取過還沒釋放而阻塞。Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個優點是可一定程度避免死鎖。

獨享鎖 VS 共享鎖

讀寫鎖中,讀鎖是共享鎖,寫鎖是獨享鎖,讀寫鎖互斥

下一章線程通信預告

同步事件這些

https://www.cnblogs.com/binfire/p/5045032.html

繼續閱讀