1. 程式人生 > >淺談C#中的同步鎖

淺談C#中的同步鎖

本文介紹C# lock關鍵字,C#提供了一個關鍵字lock,它可以把一段程式碼定義為互斥段(critical section),互斥段在一個時刻內只允許一個執行緒進入執行,而其他執行緒必須等待。

每個執行緒都有自己的資源,但是程式碼區是共享的,即每個執行緒都可以執行相同的函式。這可能帶來的問題就是幾個執行緒同時執行一個函式,導致資料的混亂,產生不可預料的結果,因此我們必須避免這種情況的發生。

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

  1. public void Function()
  2. {
  3. object lockThis = new object ();
  4. lock (lockThis)
  5. {
  6. // Access thread-sensitive resources.
  7. }
  8. }

下面是一個比較典型的使用C#  lock關鍵字的例子,其中在註釋裡說明了C# lock關鍵字的用法和用途。

  1. Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->using System;
  2. using System.Threading;
  3. namespace ThreadSimple
  4. {
  5. internal class Account
  6. {
  7. int balance; //餘額
  8. Random r=new Random();
  9. internal Account(int initial)
  10. {
  11. balance=initial;
  12. }
  13. internal int Withdraw(int amount) //取回、取款
  14. {
  15. if(balance<0)
  16. {
  17. //如果balance
    小於0則丟擲異常
  18. throw new Exception("NegativeBalance");//負的 餘額
  19. }
  20. //下面的程式碼保證在當前執行緒修改balance的值完成之前
  21. //不會有其他執行緒也執行這段程式碼來修改balance的值
  22. //因此,balance的值是不可能小於0
  23. lock(this)
  24. {
  25. Console.WriteLine("CurrentThread:"+Thread.CurrentThread.Name);
  26. //如果沒有lock關鍵字的保護,那麼可能在執行完if的條件判斷(成立)之後
  27. //另外一個執行緒卻執行了balance=balance-amount修改了balance的值
  28. //而這個修改對這個執行緒是不可見的,所以可能導致這時if的條件已經不成立了
  29. //但是,這個執行緒卻繼續執行 balance=balance-amount,所以導致balance可能小於0
  30. if(balance>=amount)
  31. {
  32. Thread.Sleep(5);
  33. balance=balance-amount;
  34. return amount;
  35. } else
  36. {
  37. return 0;
  38. //transactionrejected
  39. }
  40. }
  41. }
  42. internal void DoTransactions()//取款事務
  43. {
  44. for (int i = 0; i < 100; i++)
  45. {
  46. Withdraw(r.Next(-50, 100));
  47. }
  48. }
  49. }
  50. internal class Test
  51. {
  52. static internal Thread[] threads=new Thread[10];
  53. public static void Main()
  54. {
  55. Account acc=new Account(0);
  56. for(int i=0;i<10;i++)
  57. {
  58. Thread t=new Thread(new ThreadStart(acc.DoTransactions));
  59. threads[i]=t;
  60. }
  61. for (int i = 0; i < 10; i++)
  62. {
  63. threads[i].Name = i.ToString();
  64. }
  65. for (int i = 0; i < 10; i++)
  66. {
  67. threads[i].Start();
  68. Console.ReadLine();
  69. }
  70. }
  71. }
  72. }

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一個字串