在 C# 多线程编程里,死锁指的是两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,这些线程都将无法继续执行下去。
- 互斥条件:线程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个线程占用。如果此时还有其它线程请求该资源,则请求者只能等待,直至占有资源的线程用毕释放。
- 请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放。
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个线程 —— 资源的环形链,即线程集合 {T0,T1,T2,・・・,Tn} 中的 T0 正在等待一个 T1 占用的资源;T1 正在等待 T2 占用的资源,……,Tn 正在等待已被 T0 占用的资源。
using System;
using System.Threading;
class DeadlockExample
{
private static readonly object resource1 = new object();
private static readonly object resource2 = new object();
static void Thread1Method()
{
lock (resource1)
{
Console.WriteLine("Thread 1: Holding resource 1...");
Thread.Sleep(100);
Console.WriteLine("Thread 1: Waiting for resource 2...");
lock (resource2)
{
Console.WriteLine("Thread 1: Holding resource 1 and 2...");
}
}
}
static void Thread2Method()
{
lock (resource2)
{
Console.WriteLine("Thread 2: Holding resource 2...");
Thread.Sleep(100);
Console.WriteLine("Thread 2: Waiting for resource 1...");
lock (resource1)
{
Console.WriteLine("Thread 2: Holding resource 1 and 2...");
}
}
}
static void Main()
{
Thread thread1 = new Thread(Thread1Method);
Thread thread2 = new Thread(Thread2Method);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("Main thread exiting.");
}
}
在上述代码中,Thread1
先锁定 resource1
,接着尝试锁定 resource2
;而 Thread2
先锁定 resource2
,然后尝试锁定 resource1
。如果 Thread1
锁定了 resource1
且 Thread2
锁定了 resource2
,那么两个线程都会等待对方释放资源,从而导致死锁。
保证所有线程都按照相同的顺序获取锁,这样就能避免环路等待条件的出现。
using System;
using System.Threading;
class DeadlockAvoidanceExample
{
private static readonly object resource1 = new object();
private static readonly object resource2 = new object();
static void Thread1Method()
{
lock (resource1)
{
Console.WriteLine("Thread 1: Holding resource 1...");
Thread.Sleep(100);
lock (resource2)
{
Console.WriteLine("Thread 1: Holding resource 1 and 2...");
}
}
}
static void Thread2Method()
{
lock (resource1)
{
Console.WriteLine("Thread 2: Holding resource 1...");
Thread.Sleep(100);
lock (resource2)
{
Console.WriteLine("Thread 2: Holding resource 1 and 2...");
}
}
}
static void Main()
{
Thread thread1 = new Thread(Thread1Method);
Thread thread2 = new Thread(Thread2Method);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("Main thread exiting.");
}
}
在这个示例中,Thread1
和 Thread2
都按照先 resource1
后 resource2
的顺序获取锁,避免了死锁。
在获取锁时设置一个超时时间,如果在规定时间内未能获取到锁,就放弃并释放已持有的锁,稍后再重试。
using System;
using System.Threading;
class TimeoutLockExample
{
private static readonly object resource1 = new object();
private static readonly object resource2 = new object();
static void ThreadMethod()
{
if (Monitor.TryEnter(resource1, 1000))
{
try
{
Console.WriteLine("Thread: Holding resource 1...");
if (Monitor.TryEnter(resource2, 1000))
{
try
{
Console.WriteLine("Thread: Holding resource 1 and 2...");
}
finally
{
Monitor.Exit(resource2);
}
}
else
{
Console.WriteLine("Thread: Could not acquire resource 2.");
}
}
finally
{
Monitor.Exit(resource1);
}
}
else
{
Console.WriteLine("Thread: Could not acquire resource 1.");
}
}
static void Main()
{
Thread thread = new Thread(ThreadMethod);
thread.Start();
thread.Join();
Console.WriteLine("Main thread exiting.");
}
}
这里使用 Monitor.TryEnter
方法来尝试获取锁,并设置了 1000 毫秒的超时时间。如果在超时时间内未能获取到锁,线程会放弃并输出相应信息。
尽量缩短线程持有锁的时间,降低死锁发生的概率。可以将不需要加锁的操作放在锁的外部执行。
嵌套锁容易导致死锁,尽量减少嵌套锁的使用。如果必须使用嵌套锁,要确保锁的获取和释放顺序正确。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!