排它鎖主要用來(lái)保證,在一段時(shí)間內(nèi),只有一個(gè)線程可以訪問(wèn)某一段代碼。兩種主要類型的排它鎖是lock和Mutex。Lock和Mutex相比構(gòu)造起來(lái)更方便,運(yùn)行的也更快。但是Mutex可以在同一個(gè)機(jī)器上的不同進(jìn)程使用。
C#中的lock關(guān)鍵字,實(shí)際上是Monitor.Enter,Monitor.Exist的一個(gè)簡(jiǎn)寫。在.NET 1.0,2.0,3.0 版本的c#中,lock會(huì)被編譯成如下代碼:
Monitor.Enter(_locker); try { if (_val2 != 0) Console.WriteLine(_val1 / _val2); _val2 = 0; } finally { Monitor.Exit(_locker); }
如果你沒(méi)有調(diào)用Monitor.Enter而直接調(diào)用Monitor.Exit會(huì)引發(fā)異常。
想象一下上面這段代碼,如果再M(fèi)onitor.Enter之后,try之前,線程出現(xiàn)了異常(比如被終止),在這種情況下,finally中的Exit方法就永遠(yuǎn)不會(huì)被執(zhí)行,也就導(dǎo)致了這個(gè)鎖不會(huì)被釋放。為了避免這種情況,CLR 4.0的設(shè)計(jì)者重載了Monitor.Enter方法:
public static void Enter (object obj, ref bool lockTaken);
如果當(dāng)前線程由于某些異常導(dǎo)致鎖沒(méi)有被獲取到,lockTake值會(huì)為false,因此在CLR 4.0中,lock會(huì)被解釋成如下代碼:
bool lockTaken = false; try { Monitor.Enter(_locker, ref lockTaken); // Do your stuff... } finally { if (lockTaken) Monitor.Exit(_locker); }
Monitor也提供了了一個(gè)TryEnter方法,允許你設(shè)置一個(gè)超時(shí)時(shí)間,避免當(dāng)前線程長(zhǎng)時(shí)間獲取不到鎖而一直等待。
你需要選擇一個(gè)對(duì)所有線程都可見(jiàn)的對(duì)象進(jìn)行l(wèi)ock(obj)來(lái)確保程序能夠按照你的意圖執(zhí)行。如果比不了解C#語(yǔ)言中的某些特性,lock可能不會(huì)按照你 期望來(lái)執(zhí)行。
一個(gè)基本的規(guī)則,你需要對(duì)任意的寫操作,或者可修改的字段進(jìn)行l(wèi)ock。即使是一個(gè)賦值操作,或者累加操作,你也不能假設(shè)他是線程安全的。
例如下面代碼不是線程安全的:
class ThreadUnsafe { static int _x; static void Increment() { _x++; } static void Assign() { _x = 123; } }
你需要這樣寫:
class ThreadSafe { static readonly object _locker = new object(); static int _x; static void Increment() { lock (_locker) _x++; } static void Assign() { lock (_locker) _x = 123; } }
如果你看過(guò)一些BCL類庫(kù)里面的實(shí)現(xiàn),你可以能會(huì)發(fā)現(xiàn),某些情況下會(huì)使用InterLocked類,而不是lock,我們會(huì)在后面介紹。
你在閱讀一些文檔的時(shí)候,有的文檔可能會(huì)說(shuō)lock或者M(jìn)onitor.Enter是reentrant(可重入的),那么我們?nèi)绾卫斫鈘eentrant呢?
想象下以下代碼:
lock (locker) lock (locker) lock (locker) { // Do something... }
或者是:
Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker); // Do something... Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker);
這種情況下,只有在最后一個(gè)exit執(zhí)行后,或者執(zhí)行了相應(yīng)次數(shù)的Exit后,locker才是可獲取的狀態(tài)。
Mutex像c#中的lock一樣,但是在不同的進(jìn)程中仍然可以使用。換句話說(shuō),Mutex是一個(gè)計(jì)算機(jī)級(jí)別的鎖。因此獲取這樣一個(gè)鎖要比Monitor慢很多。
示例代碼:
using System;using System.Threading.Tasks;using System.Threading;namespace MultiThreadTest{ class OneAtATimePlease { static void Main() { // Naming a Mutex makes it available computer-wide. Use a name that's // unique to your company and application (e.g., include your URL). using (var mutex = new Mutex(false, "oreilly.com OneAtATimeDemo")) { // Wait a few seconds if contended, in case another instance // of the PRogram is still in the process of shutting down. if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false)) { Console.WriteLine("Another app instance is running. Bye!"); return; } RunProgram(); } } static void RunProgram() { Console.WriteLine("Running. Press Enter to exit"); Console.ReadLine(); } }}
Monitor和Mutex都是排他鎖,Semaphore我們常用的另外一種非排他的鎖。
我們用它來(lái)實(shí)現(xiàn)這樣一個(gè)例子:一個(gè)酒吧,最多能容納3人,如果客滿則需要等待,有客人離開(kāi),等待的人隨時(shí)可以進(jìn)來(lái)。
示例代碼:
using System;using System.Threading;class TheClub // No door lists!{ static Semaphore _sem = new Semaphore(3, 3); // Capacity of 3 static void Main() { for (int i = 1; i <= 5; i++) new Thread(Enter).Start(i); Console.ReadLine(); } static void Enter(object id) { Console.WriteLine(id + " wants to enter"); _sem.WaitOne(); Console.WriteLine(id + " is in!"); // Only three threads Thread.Sleep(1000 * (int)id); // can be here at Console.WriteLine(id + " is leaving"); // a time. _sem.Release(); }}
使用Semaphore需要調(diào)用者來(lái)控制訪問(wèn)資源,調(diào)用WaitOne來(lái)獲取資源,通過(guò)Release來(lái)釋放資源。開(kāi)發(fā)者有責(zé)任確保資源能夠正確釋放。
Semaphore在限制同步訪問(wèn)的時(shí)候非常有用,它不會(huì)像Monitor或者M(jìn)utex那樣當(dāng)一個(gè)線程訪問(wèn)某些資源時(shí),其它所有線程都需要等,而是設(shè)置一個(gè)緩沖區(qū),允許最多多少個(gè)線程同時(shí)進(jìn)行訪問(wèn)。
Semaphore也可以像Mutex一樣,跨進(jìn)程進(jìn)行同步。
本節(jié)主要總結(jié)了使用鎖進(jìn)行同步,下一節(jié)將總結(jié)使用信號(hào)量進(jìn)行同步。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注