0

0

c# 如何用c#实现哲学家进餐问题 Dining Philosophers Problem

煙雲

煙雲

发布时间:2026-01-03 12:53:02

|

482人浏览过

|

来源于php中文网

原创

用Monitor实现哲学家进餐问题的核心是打破循环等待:第4位哲学家反向取叉(先右后左),配合try/finally确保双锁安全释放、CancellationToken控制退出、Thread.Sleep模拟行为,并避免lock无法嵌套加锁的缺陷。

c# 如何用c#实现哲学家进餐问题 dining philosophers problem

Monitor 实现最简可运行的哲学家进餐

核心是避免死锁:五个哲学家不能同时拿起左边叉子。最稳妥的做法是让其中一个哲学家「反向拿叉」——先右后左,打破循环等待条件。

关键点:Monitor.EnterMonitor.Exit 必须成对出现,且必须用 try/finally 保证释放;所有叉子对象要预先创建并共享;哲学家线程需有明确退出机制(如 CancellationToken)。

  • 每个叉子用一个 object 实例表示,放在数组里:private static readonly object[] forks = Enumerable.Range(0, 5).Select(_ => new object()).ToArray();
  • i 位哲学家默认先拿 forks[i](左),再拿 forks[(i + 1) % 5](右)
  • 但第 4 位(索引 4)改为先拿 forks[(i + 1) % 5](右),再拿 forks[i](左),打破对称性
  • Thread.Sleep 模拟思考/进食时间,否则会跑得太快看不出竞争效果
using System;
using System.Threading;

class Program
{
    private static readonly object[] forks = Enumerable.Range(0, 5).Select(_ => new object()).ToArray();
    private static readonly CancellationTokenSource cts = new();

    static void Main()
    {
        var philosophers = Enumerable.Range(0, 5)
            .Select(i => new Thread(() => Philosopher(i)))
            .ToArray();

        foreach (var t in philosophers) t.Start();
        Thread.Sleep(5000);
        cts.Cancel();
        foreach (var t in philosophers) t.Join();
    }

    static void Philosopher(int id)
    {
        while (!cts.Token.IsCancellationRequested)
        {
            Console.WriteLine($"Philosopher {id} is thinking...");
            Thread.Sleep(100);

            // 左右叉子索引
            int left = id;
            int right = (id + 1) % 5;

            // 最后一位哲学家反向取叉,避免死锁
            if (id == 4)
            {
                Monitor.Enter(forks[right]);
                Monitor.Enter(forks[left]);
            }
            else
            {
                Monitor.Enter(forks[left]);
                Monitor.Enter(forks[right]);
            }

            try
            {
                Console.WriteLine($"Philosopher {id} is eating...");
                Thread.Sleep(200);
            }
            finally
            {
                Monitor.Exit(forks[left]);
                Monitor.Exit(forks[right]);
            }
        }
    }
}

为什么不用 lock 语句而用 Monitor

lock(obj) 底层就是 Monitor.Enter/Exit,但它只支持单个对象加锁。哲学家要同时持有两个叉子,必须显式控制两把锁的获取顺序和异常安全释放 —— lock 无法嵌套锁定两个不同对象而不留隐患。

  • 如果写成 lock(forks[left]) { lock(forks[right]) { ... } },在第二个 lock 失败时,第一个锁不会自动释放
  • Monitor.TryEnter 可设超时,适合做“尝试拿叉失败就放弃”策略(避免饥饿),而 lock 会一直阻塞
  • 真实系统中,你可能需要检查 Monitor.TryEnter(fork, timeout) 返回值,失败就 Thread.Sleep 后重试

Wait / Pulse 版本:更贴近原始问题语义

原始哲学家问题强调「只有左右叉都可用时才开始吃」,而不是强行抢锁。这时该用 Monitor.Wait 让线程等待条件成立,用 Monitor.PulseAll 唤醒所有等待者。

堆友
堆友

Alibaba Design打造的设计师全成长周期服务平台,旨在成为设计师的好朋友

下载

你需要为每把叉子维护一个「是否空闲」状态,并用一个全局锁保护状态检查。哲学家进入「想吃」状态后,轮询检查两把叉子是否都空闲;若不满足,Monitor.Wait 挂起自己;一旦某人吃完放下叉子,就 Monitor.PulseAll 唤醒所有人重新判断。

  • 状态变量必须是 bool[] 或类似结构,且读写必须被同一把锁保护
  • 每次 Wait 前必须在 while 循环里检查条件,防止虚假唤醒
  • PulseAll 开销比 Pulse 大,但这里无法预知谁该被唤醒,只能全唤

容易被忽略的三个实际坑

很多示例跑起来看似正常,但一加压或换环境就出问题。真正上线要考虑这些:

  • 没有设置线程名称或 ID 日志,导致并发行为无法追踪 —— 建议在 Console.WriteLine 中带上 Thread.CurrentThread.ManagedThreadId
  • 未处理 ThreadAbortException 或中断,.NET 6+ 中 Thread.Abort 已废弃,必须依赖 CancellationToken 配合 Monitor.TryEnter 超时退出
  • 所有 fork 对象都是静态的,但如果程序是 long-running service,要注意生命周期管理 —— 叉子对象本身无状态,但若未来扩展为带计数器或超时逻辑,就得考虑 Dispose 模式

哲学家问题不是为了造轮子,而是训练对锁顺序、条件竞争、唤醒丢失的直觉。代码越短,越要盯住那几行 Enter/Exit 的配对和位置。

相关专题

更多
while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

82

2023.09.25

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

473

2023.08.10

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

55

2025.12.01

console接口是干嘛的
console接口是干嘛的

console接口是一种用于在计算机命令行或浏览器开发工具中输出信息的工具,提供了一种简单的方式来记录和查看应用程序的输出结果和调试信息。本专题为大家提供console接口相关的各种文章、以及下载和课程。

410

2023.08.08

console.log是什么
console.log是什么

console.log 是 javascript 函数,用于在浏览器控制台中输出信息,便于调试和故障排除。想了解更多console.log的相关内容,可以阅读本专题下面的文章。

479

2024.05.29

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

189

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

100

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

113

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

89

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号