2018年6月25日 星期一

【C#】Lock

讓多執行緒在同時無法存取同段程式

你的帳戶有100塊,你拿了金融卡去提100,但很巧的是,你媽也拿了你的本子去提100,如果兩人同時存取同帳戶,且都提款成功,那這個帳戶就變成-100了。
  1. static decimal _balance = 100;
  2. private static readonly object _lockObj = new object();
  3.  
  4. public static void Withdraw(decimal amount)
  5. {
  6. //lock (_lockObj)
  7. //{
  8. if (amount > _balance)
  9. {
  10. throw new Exception("Insufficient funds");
  11. }
  12. _balance -= amount;
  13. //}
  14. }
  15.  
  16. static void Main(string[] args)
  17. {
  18. for (int i = 0; i < 5; i++)
  19. {
  20. Console.WriteLine("========================================");
  21. _balance = 100;
  22.  
  23. var ta = Task.Run(() =>
  24. {
  25. try
  26. {
  27. Withdraw(100);
  28. Console.WriteLine("Task A withdraw");
  29. }
  30. catch (Exception ex)
  31. {
  32. Console.WriteLine($"Task A {ex.Message}");
  33. }
  34. });
  35.  
  36. var tb = Task.Run(() =>
  37. {
  38. try
  39. {
  40. Withdraw(100);
  41. Console.WriteLine("Task B withdraw");
  42. }
  43. catch (Exception ex)
  44. {
  45. Console.WriteLine($"Task B {ex.Message}");
  46. }
  47. });
  48.  
  49. Task.WaitAll(new Task[] { ta, tb });
  50. Console.WriteLine($"balance: {_balance}");
  51.  
  52. Console.WriteLine("========================================");
  53. }
  54.  
  55. Console.ReadLine();
  56. }
試5次左右應該就有機會試出來。

為了防止多緒發生這種情況,我們會使用 Lock 進行保護,lock 的種類依應用細節不同有蠻多的,但最基本款就是
  1. lock(_lockObj)
  2. {
  3. // critical section
  4. }

第一次用 lock 很容易發生的誤會是以為 lock 的保護對象是資源,但實際上 lock 保護的對象是存取該資源的程式碼(critical section),如前 Demo 解註解,可以看到 lock {} 中的判斷領錢與實際扣錢的程式碼才是其保護對象。

整體來說可以想成這樣

至於 _lockObj 就只是要有個變數可以讓 lock statement 標現在有沒有 thread 已經進來了,細節參考: lock


沒有留言:

張貼留言