本文為原創,如需轉載,請註明作者和出處,謝謝!
上一篇:C#線程系列講座(4):同步與死結
在上一講介紹了使用lock來實現線程之間的同步。實際上,這個lock是C#的一個障眼法,在C#編譯器編譯lock語句時,將其編譯成了調用Monitor類。先看看下面的C#原始碼:
public static void MyLock()
{
lock (typeof(Program))
{
}
}
上面的代碼通過lock語句使MyLock同步,這個方法被編譯成IL後,代碼1所示。
圖1
從被標註的地區可以看到,一條lock語句被編譯成了調用Monitor的Enter和Exit方法。Monitor在System.Threading命名空間中。lock的功能就相當於直接調用Monitor的Entry方法,所不同的是,lock方法在結束後,會自動解除鎖定,當然,在IL中是調用了Monitor的Exit方法,但在C#程式中,看起來是自動解鎖的,這類似於C#中的using語句,可以自動釋放資料庫等的資源。但如果直接在C#來源程式中使用Monitor類,就必須調用Exit方法來顯式地解除鎖定。如下面的代碼所示:
Monitor.Entry(lockObj);
try
{
// lockObj的同布區
}
catch(Exception e)
{
// 異常處理代碼
}
finally
{
Monitor.Exit(lockObj); // 解除鎖定
}
Exit方法最後在finally裡調用,這樣無論在方法在發生異常、返回還是正常執行,都會執行到finally,並調用Exit方法解除鎖定。
Monitor類不僅可以完全取代lock語句(如果只使用lock語句本身的功能,最好還是直接用lock語句吧),還可以使用TryEntry方法設定一個鎖定逾時,單位是毫秒。如下面的代碼所示:
if(Monitor.TryEntry(lockObj, 1000))
{
try
{
}
finally
{
Monitor.Exit(lockObj);
}
}
else
{
// 逾時後的處理代碼
}
上面的代碼設定了鎖定逾時時間為1秒,也就是說,在1秒中後,lockObj還未被解鎖,TryEntry方法就會返回false,如果在1秒之內,lockObj被解鎖,TryEntry返回true。我們可以使用這種方法來避免死結,如下面的代碼所示:
class Program
{
private static Object objA = new Object();
private static Object objB = new Object();
public static void LockA()
{
if (Monitor.TryEnter(objA, 1000))
{
Thread.Sleep(1000);
if (Monitor.TryEnter(objB, 2000))
{
Monitor.Exit(objB);
}
else
{
Console.WriteLine("LockB timeout");
}
Monitor.Exit(objA);
}
Console.WriteLine("LockA");
}
public static void LockB()
{
if (Monitor.TryEnter(objB, 2000))
{
Thread.Sleep(2000);
if (Monitor.TryEnter(objA, 1000))
{
Monitor.Exit(objA);
}
else
{
Console.WriteLine("LockA timeout");
}
Monitor.Exit(objB);
}
Console.WriteLine("LockB");
}
public static void Main()
{
Thread threadA = new Thread(LockA);
Thread threadB = new Thread(LockB);
threadA.Start();
threadB.Start();
Thread.Sleep(4000);
Console.WriteLine("線程結束");
}
}
上面的代碼是在上一講舉的死結的例子,但在這一講將lock語句改成了TryEntry方法,而且設定了鎖定逾時間,由於在等待一定時間後,不管被鎖定的對象是否被解鎖,TryEntry方法都會返回,因此,上面的代碼是不會死結的。運行上面的代碼的結果2所示。
圖2
如果TryEntry方法的逾時時間為System.Threading.Timeout.Infinite,TryEntry方法就相當於Entry方法,如果逾時時間為0,不管是否解鎖,TryEntry方法都會立即返回。