之前有用戶模式構造和內核模式構造,前者快速,但耗費CPU;后者可以阻塞線程,但耗時、耗資源。因此.NET會有一些混合了兩者的構造,《CLR via C#》的作者給這些構造起了一個別名:混合線程同步構造(Hybrid Thread Synchronization Construct)
混合線程同步構造的例子如下:
internal sealed class SimpleHybridLock : IDisposable { PRivate int m_waiters = 0; private AutoResetEvent m_waiterLock = new AutoResetEvent(false); public void Enter() { if (Interlocked.Increment(ref m_waiters) == 1) return; m_waiterLock.WaitOne(); } public void Leave() { if (Interlocked.Decrement(ref m_waiters) == 0) return; m_waiterLock.Set(); } public void Dispose() { m_waiterLock.Dispose(); } }例子很簡單,初次使用用戶模式判斷;若有線程競爭者,則使用內核模式的進行線程阻塞。 在混合線程同步構造中有四個性能考慮點:內核對象的創建、Dispose、Enter方法、Leave方法。其中可主要考慮Enter及Leave方法。但是在.NET中其也提供了AutoResetEventSlim構造,其使用了“延遲加載”方法,即,只有當內核對象初次使用時(即第一次檢測到競爭時),才會創建AutoResetEvent,這樣可以避免性能損失。 上面的例子中,任何線程都可以調用Leave方法。所以這方法不夠嚴謹。因此可以在Enter及Leave方法中添加相關用于記錄獲取同步鎖的線程信息的字段,這樣就能保證做到只有獲得同步鎖的線程才能調用Leave方法。下面的例子進行了說明:
internal sealed class AnotherHybridLock : IDisposable { private int m_waiters = 0; private AutoResetEvent m_waiterLock = new AutoResetEvent(false); private int m_spincount = 4000; private int m_owningTheadID = 0, m_recursion = 0; public void Enter() { //若相同的線程調用Enter方法,則增加一次循環記錄后,返回 int threadID = Thread.CurrentThread.ManagedThreadId; if (threadID == m_owningTheadID) { m_recursion++; return; } //若第一個線程使用通過內核模式獲得同步鎖后,緊隨其后的第二個線程并不會立刻調用內核模式 //而是通過一個循環,碰碰運氣,看能否在循環內得到同步鎖 SpinWait spinWait = new SpinWait(); for (int spinCount = 0; spinCount < m_spincount;spinCount++ ) { if (Interlocked.CompareExchange(ref m_waiters, 1, 0) == 0) goto GotLock; spinWait.SpinOnce(); } //若還是沒有得到,只能調用內核模式,等待獲取同步鎖 if (Interlocked.Increment(ref m_waiters)>1) { m_waiterLock.WaitOne(); } GotLock: m_owningTheadID = threadID; m_recursion = 1; } public void Leave() { int threadID = Thread.CurrentThread.ManagedThreadId; if (threadID!=m_owningTheadID) { throw new SynchronizationLockException("Leave被非原線程調用!"); } //代表同一線程多次調用Leave方法,則進行--m_recursion后,直接返回 if (--m_recursion > 0) return; //代表調用Leave的線程目前只有一次Enter,因此調用Leave方法釋放同步鎖 //將當前的線程ID置位0 m_owningTheadID = 0; //代表外界無等待的線程,則直接返回 if (Interlocked.Decrement(ref m_waiters) == 0) return; //代表外界存在等在同步鎖的線程,則通過內核方法,釋放同步鎖 //使等待線程獲取同步鎖,解除阻塞 m_waiterLock.WaitOne(); } public void Dispose() { m_waiterLock.Dispose(); } }在上面的兩個例子中:有一個特點:用戶模式只能提供一個同步鎖,若還有多線程同時訪問鎖,則使用內核模式。這樣才能發揮用戶模式的“快”和內核模式的“省”。另外,第二個例子(AnotherHybridLock)與第一個相比:1、對象占用內存要大;2、Enter與Leave的性能要低。魚與熊掌不可兼得嘛!
在后一個例子中有這樣一段代碼:
SpinWait spinWait = new SpinWait();for (int spinCount = 0; spinCount < m_spincount;spinCount++ ){ if (Interlocked.CompareExchange(ref m_waiters, 1, 0) == 0) goto GotLock; spinWait.SpinOnce();}這里面有一個SpinWait結構(其實還有一個Thread.SpinWait方法),對這個結構比較感興趣,所以就去看了看MSDN的解釋。 SpinWait結構是一種可以在小腳本(low-level scenarios)中使用,并且可以避免上下文切換和內核轉換的輕量級類型。說白了,其是一種“智能”的自旋方式(這里的智能是指,內部添加了一些算法,使自旋不僅僅是簡單的自旋,還有一些其他的功能,幫助提升性能)。另外,SpinWait在自旋一段時間后,也會讓出CPU(并不是一直在自旋),這樣CPU可以處理其他的線程,而不是傻傻的一直等待自旋結束。 SpinWait結構的屬性和方法如下:
這么多屬性和方法中只有兩個最常用,一個是屬性:NextSpinWillYield;一個是方法:SpinOnce() SpinOnce()方法:方法內部有一個if…else…判斷。如果NextSpinWillYield返回true,則方法內部通過調用Thread.Sleep(0)、Thread.Sleep(1)、Thread.Yield()方法讓出CPU,否則調用Thread.Spinwait()方法使其繼續自旋。 NextSpinWillYield:其決定了調用SpinOnce方法的線程是否應該讓出CPU。若返回true,則調用SpinOnce()方法的線程會讓出CPU;返回false,則線程仍將自旋。 下面通過一個例子說明:
下面通過SpinWait結構的源代碼進行說明
這里面有一個內部變量m_count,其用來記錄SpinOnce方法的調用次數。 先看一下屬性NextSpinWillYield的源代碼:
哈哈哈,其邏輯就是:若調用SpinOnce方法10次以內,看看電腦是否為單核電腦。否則就返回true。 再看看SpinOnce()的源代碼:
public void SpinOnce(){ //在方法內部,其還是調用一次NextSpinWillYield屬性,根據屬性的結果,決定是讓出CPU還是自旋 if (this.NextSpinWillYield) //為true,則代表讓出CPU。只不過,需要根據m_count的值決定使用何種方法 { CdsSyncEtwBCLProvider.Log.SpinWait_NextSpinWillYield(); int num = (this.m_count >= 10) ? (this.m_count - 10) : this.m_count; if ((num % 20) == 0x13) { Thread.Sleep(1); } else if ((num % 5) == 4) { Thread.Sleep(0); } else { Thread.Yield(); } } else { //讓CPU進行時間為:this.m_count*16的自旋 Thread.SpinWait(((int) 4) << this.m_count); } //0x7fffffff,其為int32的最大值。即,若m_count到了最大值后,從10開始。這樣NextSpinWillYield將會一直返回true this.m_count = (this.m_count == 0x7fffffff) ? 10 : (this.m_count + 1);}以上就是SpinWait的源代碼內容。 另外,在SpinWait結構內容使用了Thread.Sleep(1)、Thread.Sleep(0)、Thread.Yield()方法,這三者方法具體的差別可參見一篇博客:三個方法的具體區別
新聞熱點
疑難解答