惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
aimingoo的专栏
aimingoo的专栏
S
SegmentFault 最新的问题
A
About on SuperTechFans
Engineering at Meta
Engineering at Meta
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 聂微东
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Blog — PlanetScale
Blog — PlanetScale
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks

博客园 - Rick Carter

官方APP太难用了,所以我写了个 OpenNas App hangfire内部执行器是同步的,会导致死锁 缓存读写代码逻辑的正确姿势 dotnet未捕获异常导致系统崩溃问题 dotnet-dump安装、收集dump和崩溃自动收集dump EFCore中巧妙利用ToQueryString()实现批插(不借助第三方包) 修复达梦EFCore驱动布尔类型兼容问题 dotnet使用redis时需要注意的问题 巧用 using 作用域(IDisposable)的生命周期包装特性 实现前后置处理 非托管内存怎么计算? dotnet集合类型性能优化的两个小儿科的知识点 一个前端树形控件联动勾选框卡顿问题及解决 .net c# Func<Task>及变体做为多播委托异步执行会另开线程的问题 关于EFCore插件API使用中踩过的坑 利用C#9.0中的record提高性能 dotnet CultureInfo遇到欧洲如俄文小数点是逗号想转点的解决办法 hangfire.entityframeworkcore这个库因为System.Threading.Timer未停止也未释放而导致的性能问题 达梦DM.Microsoft.EntityFreameworkCore查询报错invalid cast from DateTime to DateTimeOffset 达梦DOTNET驱动DM.Provider8.3.1.30495存在空字符串插入变DBNull的问题 是否可以考虑做一个dotnet应用的性能诊断工具
死锁是怎么发生的,举个简单的例子
Rick Carter · 2026-02-01 · via 博客园 - Rick Carter

死锁的示例

下面就是一个会死锁的示例代码:

// 异步死锁示例 - 不使用 TaskScheduler,仅用多个 Task 互相等待

Console.WriteLine("=== 多 Task 互相等待死锁 ===\n");

// 两个任务互相用 .Result 等待对方完成 → 死锁
var tcsA = new TaskCompletionSource<int>();
var tcsB = new TaskCompletionSource<int>();

var taskA = Task.Run(() =>
{
    Console.WriteLine($"[任务 A] 开始,线程 {Environment.CurrentManagedThreadId}");
    Console.WriteLine("[任务 A] 等待任务 B 的结果...");

    int resultB = tcsB.Task.Result; // A 阻塞等待 B

    Console.WriteLine($"[任务 A] 收到 B 的结果: {resultB}");
    tcsA.SetResult(1);
});

var taskB = Task.Run(() =>
{
    Console.WriteLine($"[任务 B] 开始,线程 {Environment.CurrentManagedThreadId}");
    Console.WriteLine("[任务 B] 等待任务 A 的结果...");

    int resultA = tcsA.Task.Result; // B 阻塞等待 A

    Console.WriteLine($"[任务 B] 收到 A 的结果: {resultA}");
    tcsB.SetResult(2);
});

Console.WriteLine("等待所有任务完成(将在此处挂起)...\n");
Console.WriteLine("死锁原因:");
Console.WriteLine("- 任务 A 用 .Result 阻塞等待 tcsB.Task 完成");
Console.WriteLine("- 任务 B 用 .Result 阻塞等待 tcsA.Task 完成");
Console.WriteLine("- tcsA.SetResult() 要等 A 等到 B 之后才会执行");
Console.WriteLine("- tcsB.SetResult() 要等 B 等到 A 之后才会执行");
Console.WriteLine("- A 等 B,B 等 A → 死锁\n");

Task.WaitAll(taskA, taskB);

Console.WriteLine("若看到这行说明未发生死锁(本示例中应看不到)");

任务 A: 阻塞在 tcsB.Task.Result → 等 B 完成
任务 B: 阻塞在 tcsA.Task.Result → 等 A 完成
              ↑_______________________________↓

  • A 要等到 tcsB.SetResult(2) 被调用才会从 .Result 返回
  • B 要等到 tcsA.SetResult(1) 被调用才会从 .Result 返回
  • 而 tcsA.SetResult(1) 在 A 里、tcsB.SetResult(2) 在 B 里,都要等对方先返回
  • 所以谁都不会先完成 → 死锁

解决办法

这个代码运行起来肯定会死锁,当然实际项目中死锁的发生比这个要复杂,但是道理相似。
解决办法就是把.Result这样的阻塞写法改成async await方式。

线程池饥饿

实际中可能是并发太高,线程池饥饿导致死锁,也就是:线程池里的工作线程数量有限。当大量任务都在阻塞等待(例如 .Result、.Wait()、Thread.Sleep()、lock 等)时,这些线程被占满且不会很快释放,新提交的任务只能在队列里等线程,就形成线程池饥饿:有活要干,但没有空闲线程来干。