【转】细说.NET中的多线程 (四 使用锁进行同步)
通過鎖來實現同步
排它鎖主要用來保證,在一段時間內,只有一個線程可以訪問某一段代碼。兩種主要類型的排它鎖是lock和Mutex。Lock和Mutex相比構造起來更方便,運行的也更快。但是Mutex可以在同一個機器上的不同進程使用。
Monitor.Enter和Monitor.Exit
C#中的lock關鍵字,實際上是Monitor.Enter,Monitor.Exist的一個簡寫。在.NET 1.0,2.0,3.0 版本的c#中,lock會被編譯成如下代碼:
| 1 2 3 4 5 6 7 | Monitor.Enter(_locker); try { ????if?(_val2 != 0) Console.WriteLine(_val1 / _val2); ????_val2 = 0; } finally?{ Monitor.Exit(_locker); } |
如果你沒有調用Monitor.Enter而直接調用Monitor.Exit會引發異常。
LockTaken版本:
想象一下上面這段代碼,如果再Monitor.Enter之后,try之前,線程出現了異常(比如被終止),在這種情況下,finally中的Exit方法就永遠不會被執行,也就導致了這個鎖不會被釋放。為了避免這種情況,CLR 4.0的設計者重載了Monitor.Enter方法:
| 1 | public?static?void?Enter (object?obj,?ref?bool?lockTaken); |
?
如果當前線程由于某些異常導致鎖沒有被獲取到,lockTake值會為false,因此在CLR 4.0中,lock會被解釋成如下代碼:
| 1 2 3 4 5 6 7 8 9 10 | bool?lockTaken =?false; try { ? ????Monitor.Enter(_locker,?ref?lockTaken); ? ????// Do your stuff... } ? finally?{?if?(lockTaken) Monitor.Exit(_locker); } |
TryEnter
Monitor也提供了了一個TryEnter方法,允許你設置一個超時時間,避免當前線程長時間獲取不到鎖而一直等待。
選擇正確的同步對象
你需要選擇一個對所有線程都可見的對象進行lock(obj)來確保程序能夠按照你的意圖執行。如果你不了解C#語言中的某些特性,lock可能不會按照你 期望來執行。
什么時候使用lock
一個基本的規則,你需要對任意的寫操作,或者可修改的字段進行lock。即使是一個賦值操作,或者累加操作,你也不能假設他是線程安全的。
例如下面代碼不是線程安全的:
| 1 2 3 4 5 6 | class?ThreadUnsafe { ????static?int?_x; ????static?void?Increment() { _x++; } ????static?void?Assign() { _x = 123; } } |
?
你需要這樣寫:
| 1 2 3 4 5 6 7 8 | class?ThreadSafe { ????static?readonly?object?_locker =?new?object(); ????static?int?_x; ? ????static?void?Increment() {?lock?(_locker) _x++; } ????static?void?Assign() {?lock?(_locker) _x = 123; } } |
如果你看過一些BCL類庫里面的實現,你可以能會發現,某些情況下會使用InterLocked類,而不是lock,我們會在后面介紹。
關于嵌套鎖或者reentrant
你在閱讀一些文檔的時候,有的文檔可能會說lock或者Monitor.Enter是reentrant(可重入的),那么我們如何理解reentrant呢?
想象下以下代碼:
| 1 2 3 4 5 6 | lock?(locker) ????lock?(locker) ????????lock?(locker) ????????{ ????????????// Do something... ????????} |
?
或者是:
| 1 2 3 | Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker); // Do something... Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker); |
這種情況下,只有在最后一個exit執行后,或者執行了相應次數的Exit后,locker才是可獲取的狀態。
Mutex
Mutex像c#中的lock一樣,但是在不同的進程中仍然可以使用。換句話說,Mutex是一個計算機級別的鎖。因此獲取這樣一個鎖要比Monitor慢很多。
示例代碼:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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(); ????????} ????} } |
Semaphore
Monitor和Mutex都是排他鎖,Semaphore我們常用的另外一種非排他的鎖。
我們用它來實現這樣一個例子:一個酒吧,最多能容納3人,如果客滿則需要等待,有客人離開,等待的人隨時可以進來。
示例代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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需要調用者來控制訪問資源,調用WaitOne來獲取資源,通過Release來釋放資源。開發者有責任確保資源能夠正確釋放。
Semaphore在限制同步訪問的時候非常有用,它不會像Monitor或者Mutex那樣當一個線程訪問某些資源時,其它所有線程都需要等,而是設置一個緩沖區,允許最多多少個線程同時進行訪問。
Semaphore也可以像Mutex一樣,跨進程進行同步。
?
本節主要總結了使用鎖進行同步,下一節將總結使用信號量進行同步。?????
總結
以上是生活随笔為你收集整理的【转】细说.NET中的多线程 (四 使用锁进行同步)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 俩巨头“开撕”!阿迪达斯起诉耐克专利侵权
- 下一篇: 山东现滚轴云似金箍棒横跨海面!专家称很罕