广州网站建设晋江网站建设

南通市淳键化纤有限公司 2026/09/09 20:51:39

在 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;
  • 超时失败时打日志、回滚操作,相当于“主动放弃资源”,避免挂死。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

建设网站wap网站建设

Bodymovin插件终极使用手册:从零开始掌握动画导出技术【免费下载链接】bodymovin-extensionBodymovin UI extension panel项目地址: ht

2026/06/30 13:35:36

永州网站建设宿迁网站建设

深入解析 Exim 邮件传输代理:功能、配置与优化在当今数字化的时代,邮件作为一种重要的通信方式,其传输的稳定性和高效性至关重要。Exim 作为一款功能强大的邮件传输代理(MTA),为用户提供了丰富的

2026/06/30 13:01:34

合川网站建设长春网站建设

Ubuntu系统实用技巧大揭秘1. 文件与文件夹操作1.1 添加注释在Ubuntu系统中,任何文件或文件夹都可以添加注释。操作步骤如下:1. 右键单击文件或文件夹。2. 从弹出菜单中选择“属性”。3.

2026/06/30 14:17:39

沧州网站建设平台网站建设

TTSOP跨境互联一站式提供TikTok账号 + 静态住宅IP,专为带货直播打造爆量通道。在TikTok这个日活数十亿的短视频宇宙中,每一条内容都像一颗投入信息海洋

2026/06/30 11:07:23

西安网站建设摄影网站建设

OCAuxiliaryTools完整使用指南:3步搞定OpenCore配置【免费下载链接】OCAuxiliaryToolsCross-platform GUI management to

2026/06/30 12:30:02

建设网站制作邢台网站建设

解锁阅读3.0的终极书源宝库【免费下载链接】最新1629个精品书源.json阅读3.0最新1629个精品书源.json阅读3.0项目地址: https://gitcode.com/open-sourc

2026/06/30 10:24:50

无锡网站建设网站建设计划书

如何快速部署Moonraker:3D打印远程控制完整指南【免费下载链接】moonrakerWeb API Server for Klipper项目地址: https://gitcode.

2026/06/30 12:16:30

广州市网站建设六安网站建设

在 AI 逐步参与接口开发和测试的当下,自动化测试的门槛正在被不断拉低。很多过去需要反复手动操作、人工造数据的事情,现在都可以交给 AI 来处理,而 Apif

2026/06/30 11:09:54

机票网站建设新乡网站建设

Obsidian研究笔记模板:3步构建科研工作流的智能中枢【免费下载链接】obsidian_vault_template_for_researcherThis is an vault t

2026/06/30 12:42:32

黄石网站建设网站建设维护

Path of Building PoE2珠宝构建:从实战案例到思维升级的完整路径【免费下载链接】PathOfBuilding-PoE2项目地址: https://gitcode.com

2026/06/30 11:34:26