先簡單介紹一下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