2018年6月25日 星期一

【C#】Lock

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

你的帳戶有100塊,你拿了金融卡去提100,但很巧的是,你媽也拿了你的本子去提100,如果兩人同時存取同帳戶,且都提款成功,那這個帳戶就變成-100了。
static decimal _balance = 100;
private static readonly object _lockObj = new object();

public static void Withdraw(decimal amount)
{
    //lock (_lockObj)
    //{
        if (amount > _balance)
        {
            throw new Exception("Insufficient funds");
        }
        _balance -= amount;
    //}
}

static void Main(string[] args)
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("========================================");
        _balance = 100;

        var ta = Task.Run(() =>
        {
            try
            {
                Withdraw(100);
                Console.WriteLine("Task A withdraw");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Task A {ex.Message}");
            }
        });

        var tb = Task.Run(() =>
        {
            try
            {
                Withdraw(100);
                Console.WriteLine("Task B withdraw");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Task B {ex.Message}");
            }
        });

        Task.WaitAll(new Task[] { ta, tb });
        Console.WriteLine($"balance: {_balance}");

        Console.WriteLine("========================================");
    }

    Console.ReadLine();
}
試5次左右應該就有機會試出來。

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

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

整體來說可以想成這樣

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


沒有留言:

張貼留言