昆山网站建设巴中网站建设

杭州边趣网络 2026/09/09 20:04:58

在 C# / .NET 里,死锁的“四个必要条件”其实很好理解,把它们看成导致“互相卡住”的四块拼图——四块都存在时,才会真的卡死。只要你在设计里打碎其中一块,就能避免死锁。
下面逐个讲,每个条件都配一段通俗解释和 C# 示例。

  1. 互斥: 某个资源在同一时刻只能被一个线程使用(如 lock 锁定的对象)
  2. 持有并等待: 线程已经拿着一个资源,还不放手;同时又在等待另一个资源。
  3. 不可抢占:线程持有的资源不能被强制剥夺,只能主动释放。
  4. 循环等待: 线程 A 等待线程 B 的资源,线程 B 等待线程 A 的资源,形成闭环
  5. 互斥条件(Mutual Exclusion)
    1.1 概念(白话版)
    互斥意思是:某个资源在同一时刻只能被一个线程使用。
    类比:
  • 一间厕所只有一个坑位(资源);
  • A 进去上厕所时,门锁上,B 只能在外等;
  • 厕所就是“互斥资源”。

在 C# 中,常见的互斥资源包括:

  • 被 lock 保护的临界区;
  • Mutex、Semaphore(1,1) 等;
  • 文件句柄、数据库连接等有限资源。
private readonly object _lockObj = new object(); private int _counter = 0; public void Increase() { lock (_lockObj) // 这里就是互斥 { // 同一时刻,只能有一个线程在这段代码里 _counter++; Thread.Sleep(100); // 模拟一些工作 } }
  • lock (_lockObj) 保证 _counter 的操作是“互斥”的;
  • 如果多个线程同时调用 Increase(),会按顺序排队进入 lock 里面。

为什么互斥是死锁必要条件?
如果一个资源可以被多个线程同时使用(比如只读资源、纯函数),线程之间就不会因为“抢资源”而卡住,也就不会形成死锁。


  1. 占有且等待(Hold and Wait)
    2.1 概念(白话版)

占有且等待意思是:线程已经拿着一个资源,还不放手;同时又在等待另一个资源。

类比:

  • A 拿着钥匙 1(资源 1),还要再借钥匙 2(资源 2)才能开第二个房间;
  • A 没拿到钥匙 2 之前,不会把钥匙 1 还回去;
  • 此时 A 就是“占有(钥匙 1)且等待(钥匙 2)”。

2.2 示例

private readonly object _lockA = new object(); private readonly object _lockB = new object(); public void Method1() { lock (_lockA) // 占有资源 A { Thread.Sleep(100); // 模拟业务 lock (_lockB) // 在占有 A 的情况下继续等待 B { // 使用资源 A 和 B 的临界区 } } }

在 Method1 中,线程先 lock (_lockA),在持有 A 的同时又 lock (_lockB),典型的“占有且等待”。

如果同时还有一个方法反过来(常见死锁写法):

public void Method2() { lock (_lockB) // 占有资源 B { Thread.Sleep(100); lock (_lockA) // 在占有 B 的情况下等待 A { // 使用资源 A 和 B 的临界区 } } }

当线程 T1 调用 Method1()、线程 T2 调用 Method2() 时,就具备了“占有且等待”的条件:

  • T1:占有 A 等待 B
  • T2:占有 B 等待 A

如果不存在“占有且等待”会怎样?

  • 比如设计成:线程请求新资源时,必须先释放已经持有的资源;
  • 那么线程不会同时持有多个资源,也就不存在资源之间形成“环形依赖”,死锁就难以出现。

3.不可抢占(No Preemption)
3.1 概念(白话版)

不可抢占意思是:线程持有的资源不能被强制剥夺,只能主动释放。
类比:

  • A 进了厕所,把门从里面反锁;
  • B 再急,也不能把门强行踹开,只能等 A 出来;
  • 只有 A 自己愿意开门,厕所才会被释放。

在 C# 中:

  • lock、Monitor、Mutex 等都不支持“强制释放”;
  • 如果线程挂死在临界区,锁就一直不释放。
    3.2 C# 示例
private readonly object _lockObj = new object(); public void DoWork() { lock (_lockObj) { // 一旦进来,其他线程就必须等待 // 这里如果发生异常或者死循环,锁就一直不释放 Thread.Sleep(Timeout.Infinite); // 模拟挂死 } } public void OtherWork() { lock (_lockObj) { // 永远等不到 Console.WriteLine("永远不会到达这里"); } }
  • 第一个线程进入 DoWork,获取 _lockObj 后一直 Sleep(模拟挂死);
  • 第二个线程在 OtherWork 中想获取同一个锁,只能永远等待。

如果资源可以被抢占,会怎样?
理论上,如果系统能“看你占着厕所不干事,就强制踢你出来”,就能打破死锁;
但在一般编程语言里,这样的强行剥夺很难安全实现,所以默认都是不可抢占。


  1. 循环等待(Circular Wait)
    4.1 概念(白话版)
    循环等待是死锁最直观的表现形式:线程 A 等待线程 B 的资源,线程 B 等待线程 A 的资源,多个线程之间形成了一个“环形的等待链”。

类比:

  • A 拿着钥匙 1 等钥匙 2;
  • B 拿着钥匙 2 等钥匙 3;
  • C 拿着钥匙 3 等钥匙 1;
  • A → B → C → A,形成一个等待环,谁也等不到。

两线程最简单的循环等待:

  • T1:持有 A,等待 B;
  • T2:持有 B,等待 A;
  • 等待关系:T1 → B → T2 → A → T1,形成环。

4.2 C# 示例(经典死锁示例)

private readonly object _lockA = new object(); private readonly object _lockB = new object(); public void Thread1Work() { lock (_lockA) // T1 持有 A { Console.WriteLine("T1:拿到 A,准备拿 B..."); Thread.Sleep(100); // 让 T2 有时间先拿 B lock (_lockB) // T1 等待 B { Console.WriteLine("T1:拿到 B"); } } } public void Thread2Work() { lock (_lockB) // T2 持有 B { Console.WriteLine("T2:拿到 B,准备拿 A..."); Thread.Sleep(100); // 让 T1 有时间先拿 A lock (_lockA) // T2 等待 A { Console.WriteLine("T2:拿到 A"); } } } 启动代码: public void Run() { var t1 = new Thread(Thread1Work); var t2 = new Thread(Thread2Work); t1.Start(); t2.Start(); t1.Join(); t2.Join(); }

执行过程:

  1. T1 先进入 Thread1Work(),获得 _lockA;
  2. T2 进入 Thread2Work(),获得 _lockB;
  3. T1 在尝试 lock (_lockB) 时,被阻塞,因为 B 已被 T2 持有;
  4. T2 在尝试 lock (_lockA) 时,被阻塞,因为 A 已被 T1 持有;
  5. T1 等 T2 的 B,T2 等 T1 的 A → 构成环形等待。

这就是一个完整的死锁,四个条件全部满足:

  1. 互斥:_lockA、_lockB 都是互斥资源(lock);
  2. 占有且等待:
  • T1:占有 A 等 B
  • T2:占有 B 等 A
  1. 不可抢占:A、B 在被持有时不能被强制夺回;
  2. 循环等待:T1 等 B(T2 持有)→ T2 等 A(T1 持有)→ 形成环。

  1. 综合示例:四个条件如何共同导致死锁?

我们用一个稍微完整的例子,把四个条件串起来看:

private readonly object _lockA = new object(); private readonly object _lockB = new object(); public void TaskA() { lock (_lockA) // 1. 互斥:获取互斥资源 A { Console.WriteLine("TaskA got A"); Thread.Sleep(100); // 2. 占有且等待:在持有 A 的状态下继续等待 B lock (_lockB) { Console.WriteLine("TaskA got B"); } } } public void TaskB() { lock (_lockB) // 1. 互斥:获取互斥资源 B { Console.WriteLine("TaskB got B"); Thread.Sleep(100); // 2. 占有且等待:在持有 B 的状态下继续等待 A lock (_lockA) { Console.WriteLine("TaskB got A"); } } } 运行: public void RunDeadlockDemo() { var tA = new Thread(TaskA); var tB = new Thread(TaskB); tA.Start(); tB.Start(); tA.Join(); tB.Join(); }

四个条件一一对照:

  1. 互斥条件
  • _lockA 和 _lockB 都通过 lock 实现互斥访问。
  1. 占有且等待
  • TaskA:先持有 _lockA,然后等待 _lockB;
  • TaskB:先持有 _lockB,然后等待 _lockA。
  1. 不可抢占
  • 一旦 TaskA 拿到 _lockA,除非它离开 lock 块,否则没有任何办法强制释放;
  • 同理,TaskB 拿到 _lockB 后也只能等它自己释放。
  1. 循环等待
  • TaskA 等 TaskB 手里的 _lockB;
  • TaskB 等 TaskA 手里的 _lockA;
  • 形成一个闭合等待圈。

只要这四个条件同时存在,死锁就有可能发生;
而真正发生死锁时,通常就是你没控制好锁的顺序或阻塞方式(比如 async 里用 .Result)。

  1. 怎样利用这“四个条件”去避免死锁?
  2. 破坏循环等待:
  • 统一锁顺序:所有地方获取多个锁时,规定好顺序,比如总是先锁 A 再锁 B;
  • 不允许有反过来“先 B 后 A”的情况。
  1. 缓和占有且等待:
  • 尽量避免:持有一个锁时再去申请另一个锁;
  • 多数业务可以重构为:先计算好数据,再在短小的临界区一次性拿锁、更新状态。
  1. 避免不必要的互斥:
  • 尽量使用不可变对象、多读少写、分区锁等方式减少需要“排队”的资源。
  1. 增加“抢占”效果(实质上是避免永久等待):
  • 使用 Monitor.TryEnter + 超时;
  • async 场景用 SemaphoreSlim.WaitAsync + CancellationToken;
  • 超时失败时打日志、回滚操作,相当于“主动放弃资源”,避免挂死。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

吉安网站建设高端品牌网站建设

语音克隆是否需要授权?法律边界正在形成在短视频、虚拟主播和AI配音日益普及的今天,你有没有想过:一段仅凭5秒录音就能完美复刻你声音的技术,正悄然

2026/06/30 11:48:57

中山网站建设泸州网站建设

k6性能测试革命:重新定义负载测试的架构与实践【免费下载链接】k6A modern load testing tool, using Go and JavaScript - https:

2026/06/30 09:50:17

淮安网站建设宝山网站建设

红外避障传感器在Proteus中的仿真实战:从原理到智能小车闭环控制你有没有过这样的经历?焊好电路、接上电源,结果单片机一通电就“冒烟”;或者反

2026/06/30 12:36:02

网站建设免费网站建设服务器

ANSYS Fluent官方教程:从入门到精通的全方位学习指南【免费下载链接】ANSYSFluent官方教程下载ANSYS Fluent是一款功能强大的流体力学仿真软件,广

2026/06/30 12:26:31

网站正在建设中网站建设运营

智慧树自动学习插件完整使用指南:三步实现高效刷课体验【免费下载链接】zhihuishu智慧树刷课插件,自动播放下一集、1.5倍速度、无声项目地址: https://gitc

2026/06/30 13:41:06

网站建设技术咸阳网站建设

为VLC播放器注入视觉活力:VeLoCity主题皮肤全面体验【免费下载链接】VeLoCity-Skin-for-VLCCastom skin for VLC Player项目地址: ht

2026/06/30 12:00:59

网站建设步骤网站建设专业的公司

串口助手终极指南:从零开始掌握Serial Port Utility【免费下载链接】串口助手SerialPortUtility使用说明Serial Port Utility是一款功能强大

2026/06/30 13:48:07

上海门户网站建设网站建设 北京

目录前言选题背景意义数据集构建功能模块介绍图像处理模块缺陷检测模块算法理论YOLOX算法YOLOv5算法MobileNetV2算法相关代码介绍数据加载与预处理模型定义代码模型训练代码重难点和创新点相关

2026/06/30 11:40:56