首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

哲學家進餐問題:C# 多執行緒同步化完整實作

     編輯:關於C語言

問題背景

哲學家進餐問題是作業系統經典同步化問題之一:五位哲學家圍坐圓桌,每人左右各有一根筷子,進餐需同時拿到左右兩根筷子。若所有人同時拿起左筷,將陷入死結。本文展示使用 C# Mutex 與 WaitHandle 實作的無死結解決方案,並以 Windows Forms 視覺化呈現各哲學家狀態。

架構設計

核心類別結構

  • Form1:主視窗,管理 5 個 Philosopher 執行緒與 5 個 Mutex 筷子
  • WorkerThread:抽象基類,封裝 Thread 啟動/停止邏輯
  • Philosopher:繼承 WorkerThread,實作哲學家行為(思考、等待、進餐)
  • PhilosopherData:結構體,攜帶哲學家 ID、左右筷子、食物量等資料
  • MessageArrivedEventArgs:事件參數,傳遞狀態變更至 UI

筷子資源配置

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.WaitAll 避免死結

關鍵在於原子性取得多個鎖:

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!");
}

UI 事件處理

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 從根本解決死結,無需引入管程或複雜協調演算法
  • 事件驅動架構將業務邏輯與 UI 解耦
  • WorkerThread 基類提供可復用的執行緒生命週期管理
  • 隨機睡眠時間模擬真實世界的不確定性

潛在改進

  • 加入公平性機制防止飢餓
  • 支援動態調整哲學家數量
  • 使用 SemaphoreSlim 或 async/await 現代化非阻塞實作
  • 加入視覺化圓桌佈局與筷子狀態顯示

總結

此實作展示了 .NET 同步化原語的強大表達力。透過 WaitHandle.WaitAll 的原子多鎖獲取,優雅化解經典死結問題,同時結合事件模式實現乾淨的 UI 通知架構,是多執行緒教學與實務參考的優良範例。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved