1. 程式人生 > >C# 線程同步

C# 線程同步

odi 種類型 OS 次數 結果 ron HR 監視器 pre

volatile關鍵字

  volatile是最簡單的一種同步方法,當然簡單是要付出代價的。它只能在變量一級做同步,volatile的含義就是告訴處理器, 不要將我放入工作內存, 請直接在主存操作我。(【轉自www.bitsCN.com 】)因此,當多線程同時訪問該變量時,都將直接操作主存,從本質上做到了變量共享。

  能夠被標識為volatile的必須是以下幾種類型:(摘自MSDN)

Any reference type.
Any pointer type (in an unsafe context).
The types sbyte, byte, short, ushort, int, uint, char, float, bool.
An enum type with an enum base type of byte, sbyte, short, ushort, int, or uint.

lock關鍵字

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

  lock的參數必須是基於引用類型的對象,不要是基本類型像bool,int什麽的,這樣根本不能同步,原因是lock的參數要求是對象,如果傳入int,勢必要發生裝箱操作,這樣每次lock的都將是一個新的不同的對象。最好避免使用public類型或不受程序控制的對象實例,因為這樣很可能導致死鎖。特別是不要使用字符串作為lock的參數,因為字符串被CLR“暫留”,就是說整個應用程序中給定的字符串都只有一個實例,因此更容易造成死鎖現象。建議使用不被“暫留”的私有或受保護成員作為參數。其實某些類已經提供了專門用於被鎖的成員,比如Array類型提供SyncRoot,許多其它集合類型也都提供了SyncRoot。
  所以,使用lock應該註意以下幾點: 

  1、如果一個類的實例是public的,最好不要lock(this)。因為使用你的類的人也許不知道你用了lock,如果他new了一個實例,並且對這個實例上鎖,就很容易造成死鎖。

  2、如果MyType是public的,不要lock(typeof(MyType))

  3、永遠也不要lock一個字符串

 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 類來實現的。下面兩段代碼是等效的:

Code 
lock (x)
{
DoSomething();
} 

等效於

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

MethodImplAttribute

  如果整個方法內部的代碼都需要上鎖的話,使用MethodImplAttribute屬性會更簡單一些。這樣就不用在方法內部加鎖了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空間System.Runtime.CompilerServices 裏面。但要註意這個屬性會使整個方法加鎖,直到方法返回,才釋放鎖。因此使用上不太靈活。如果要提前釋放鎖,則應該使用Monitor或lock。我們來看一個例子:

  Code
[MethodImpl(MethodImplOptions.Synchronized)]
public void DoSomeWorkSync()
{
Console.WriteLine( " DoSomeWorkSync() -- Lock held by Thread " + 
Thread.CurrentThread.GetHashCode());
Thread.Sleep( 1000 );
Console.WriteLine( " DoSomeWorkSync() -- Lock released by Thread " + 
Thread.CurrentThread.GetHashCode());
}
public void DoSomeWorkNoSync()
{
Console.WriteLine( " DoSomeWorkNoSync() -- Entered Thread is " + 
Thread.CurrentThread.GetHashCode());
Thread.Sleep( 1000 );
Console.WriteLine( " DoSomeWorkNoSync() -- Leaving Thread is " + 
Thread.CurrentThread.GetHashCode());
}

[STAThread]
static void Main( string [] args)
{
MethodImplAttr testObj = new MethodImplAttr();
Thread t1 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
Thread t2 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
t1.Start();
t2.Start();
Thread t3 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
Thread t4 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
t3.Start();
t4.Start();

Console.ReadLine();
}

這裏,我們有兩個方法,我們可以對比一下,一個是加了屬性MethodImpl的DoSomeWorkSync(),一個是沒加的DoSomeWorkNoSync()。在方法中Sleep(1000)是為了在第一個線程還在方法中時,第二個線程能夠有足夠的時間進來。對每個方法分別起了兩個線程,我們先來看一下結果:

可以看出,對於線程1和2,也就是調用沒有加屬性的方法的線程,當線程2進入方法後,還沒有離開,線程1有進來了,這就是說,方法沒有同步。我們再來看看線程3和4,當線程3進來後,方法被鎖,直到線程3釋放了鎖以後,線程4才進來。

C# 線程同步