C# Lock 概論

先簡單介紹一下Lock,如同大家在聽到多執行緒時常聽到的哲學家故事,今天一個圓桌上有多位哲學家,每個哲學家中間都擺著一支筷子,當個哲學家要吃飯,必須拿起哲學家左右兩邊的筷子吃飯,而其他兩旁的哲學家這時候只能負責思考,那如果兩旁的哲學家這時也想要吃飯,就會變成第一位哲學家拿起了右邊的筷子,第二位哲學家也拿起了右邊的筷子……,最後所有哲學家都拿起了一支右手的筷子,也全都在等別的筷子,而這樣就會造成死結(Deadlock),每個哲學家就像一個執行緒一樣,在互相搶資源(筷子),這時就要使用Lock,使得CPU避免正在執行的程式與其他執行中的程式互相干擾,講了這麼多我們來看看實際的範例,以下使用微軟lock 陳述式中所提供的範例做說明。

 

下方程式為模擬一個帳戶(Account)一直被扣隨機數目後,扣至零塊錢的情形。

// using System.Threading; 宣告Thread所需要的參考class Account

{

    private Object thisLock = new Object();

    int balance;

 

    Random r = new Random();

 

    public Account(int initial)

    {

        balance = initial;

    }

 

    int Withdraw(int amount)

    {

 

        // 假如lock在,則永遠不會執行這段程式碼

        if (balance < 0)

        {

            throw new Exception("Negative Balance");

        }

 

        // 註解下方lock後程式的執行結果將會錯亂

        lock (thisLock)

        {

            if (balance >= amount)

            {

                Console.WriteLine("扣款前金額 :  " + balance);

                Console.WriteLine("扣款金額        : -" + amount);

                balance = balance - amount;

                Console.WriteLine("剩餘金額  :  " + balance);

                return amount;

            }

            else

            {

                return 0; // 交易取消

            }

        }

    }

 

    public void DoTransactions()

    {

        for (int i = 0; i < 100; i++)

        {

            Withdraw(r.Next(1, 100));

        }

    }

}

 

class Test

{

    static void Main()

    {

        Thread[] threads = new Thread[10];

        Account acc = new Account(1000);

        for (int i = 0; i < 10; i++)

        {   //建立執行緒

            Thread t = new Thread(new ThreadStart(acc.DoTransactions));

            threads[i] = t;

        }

        for (int i = 0; i < 10; i++)

        {   //運行執行緒

            threads[i].Start();

        }

       

        //阻擋主執行緒,等待其他執行緒都執行完成

        foreach (var t in threads)

            t.Join();

    }

}

上方程式運行結果會看到一個帳戶逐漸被扣至零,但如果把lock拿掉扣錢會出現錯亂,甚至會有扣至balance少於零的情形發生,從此範例程式,可以知道lock對於多執行緒的重要性。

接下來先簡單介紹好用的System.Collections.Concurrent 命名空間,有下列這些可以用

ConcurrentBag<T>代表安全執行緒的未排序物件集合。

對應List<T>。

ConcurrentDictionary<TKey, TValue>代表索引鍵/值組的安全執行緒集合,此集合可由多個執行緒並行存取。

對應Dictionary<TKey, TValue>。

ConcurrentQueue<T>表示安全執行緒的先進先出 (FIFO) 集合。

對應Queue。

ConcurrentStack<T>表示安全執行緒的後進先出 (Last In-First Out,LIFO) 集合。

對應Stack<T>。

引用微軟介紹:System.Collections.Concurrent 命名空間提供數個安全執行緒集合類別,每當有多個執行緒同時存取集合時,應該使用這些類別來代替 System.Collections 和 System.Collections.Generic 命名空間中的對應類型。

 

這些好用的類別適合使用在多執行緒的情形下,甚至不需要像平常的情況一樣,使用lock讓CPU執行緒特別在那等待,使用方法等待下次介紹。

 

結語:這次初步介紹多執行緒中lock的使用方法,多執行緒可以使得程式運行時間大大縮短,但即使是簡單的程式,套用了多執行緒後,往往會多出許多不可預測的結果,有些結果甚至是你在Debug中不會遇到的,像我在寫簡訊系統時,常常為了節省運轉時間而使用多執行緒去跑,但在內部多使用者同時測試時,有時就會出現些沒有想過的錯誤,這時候lock, Concurrent這些工具就可以派上用場,下次再介紹些更好用的工具,來解決多執行緒所遇到的各種問題。

 

參考:https://msdn.microsoft.com/zh-tw 微軟MSDN


發表迴響

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料