哲學家進餐問題是作業系統經典同步化問題之一:五位哲學家圍坐圓桌,每人左右各有一根筷子,進餐需同時拿到左右兩根筷子。若所有人同時拿起左筷,將陷入死結。本文展示使用 C# Mutex 與 WaitHandle 實作的無死結解決方案,並以 Windows Forms 視覺化呈現各哲學家狀態。
Philosopher 執行緒與 5 個 Mutex 筷子Thread 啟動/停止邏輯WorkerThread,實作哲學家行為(思考、等待、進餐)Mutex[] chopSticks = new Mutex[5];
for (int i = 0; i < 5; i++)
chopSticks[i] = new Mutex(false);
for (int i = 0; i < 5; i++) {
PhilosopherData pd;
pd.PhilosopherId = i;
pd.RightChopStick = chopSticks[(i + 1) % 5]; // 右手邊筷子
pd.LeftChopStick = chopSticks[(i + 4) % 5]; // 左手邊筷子(模組運算避免負索引)
pd.AmountToEat = 5;
pd.TotalFood = 35;
p[i] = new Philosopher(pd);
p[i].MessageArrival += ShowMessage;
}
關鍵在於原子性取得多個鎖:
WaitHandle[] chopSticks = new WaitHandle[] { pd.LeftChopStick, pd.RightChopStick };
WaitHandle.WaitAll(chopSticks); // 同時取得兩根筷子,或全部等待
WaitAll 確保要么同時獲得兩把鎖,要么完全不獲得,避免「持有左筷等右筷」的循環等待條件,從根本上消除死結可能。
protected override void Run() {
PhilosopherData pd = (PhilosopherData)Data;
Random r = new Random(pd.PhilosopherId);
MessageArrival(this, new MessageArrivedEventArgs(READY, pd));
WaitHandle[] chopSticks = new WaitHandle[] { pd.LeftChopStick, pd.RightChopStick };
while (pd.TotalFood > 0) {
WaitHandle.WaitAll(chopSticks); // 1. 等待並取得雙筷
MessageArrival(this, new MessageArrivedEventArgs(EATING, pd)); // 2. 進餐
pd.TotalFood -= pd.AmountToEat;
Thread.Sleep(r.Next(1000, 5000)); // 模擬進餐時間
MessageArrival(this, new MessageArrivedEventArgs(THINKING, pd)); // 3. 思考
pd.RightChopStick.ReleaseMutex(); // 4. 釋放筷子
pd.LeftChopStick.ReleaseMutex();
Thread.Sleep(r.Next(1000, 5000)); // 模擬思考時間
}
MessageArrival(this, new MessageArrivedEventArgs(FINISHED, pd));
if (++finished == 4) MessageBox.Show("All Finished!");
}
public void ShowMessage(object sender, MessageArrivedEventArgs e) {
switch (e.type) {
case Philosopher.READY:
listBox1.Items.Add($"Philosopher({e.philosopherData.PhilosopherId}) ready.");
break;
case Philosopher.EATING:
listBox1.Items.Add($"Philosopher({e.philosopherData.PhilosopherId}) eating {e.philosopherData.AmountToEat} of {e.philosopherData.TotalFood} food.");
break;
case Philosopher.THINKING:
listBox1.Items.Add($"Philosopher({e.philosopherData.PhilosopherId}) thinking.");
break;
case Philosopher.FINISHED:
listBox1.Items.Add($"Philosopher({e.philosopherData.PhilosopherId}) finished.");
break;
}
}
WaitHandle.WaitAll 從根本解決死結,無需引入管程或複雜協調演算法WorkerThread 基類提供可復用的執行緒生命週期管理SemaphoreSlim 或 async/await 現代化非阻塞實作此實作展示了 .NET 同步化原語的強大表達力。透過 WaitHandle.WaitAll 的原子多鎖獲取,優雅化解經典死結問題,同時結合事件模式實現乾淨的 UI 通知架構,是多執行緒教學與實務參考的優良範例。