代码改变世界

c#什么是死锁,怎么避免死锁

  钟铧若岩  阅读(29)  评论(0编辑  收藏  举报

什么是死锁


在 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,那么两个线程都会等待对方释放资源,从而导致死锁。

如何避免死锁

1. 按顺序获取锁

 

保证所有线程都按照相同的顺序获取锁,这样就能避免环路等待条件的出现。
 
复制代码
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 的顺序获取锁,避免了死锁。

2. 使用超时机制

 

在获取锁时设置一个超时时间,如果在规定时间内未能获取到锁,就放弃并释放已持有的锁,稍后再重试。
复制代码
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 毫秒的超时时间。如果在超时时间内未能获取到锁,线程会放弃并输出相应信息。

3. 减少锁的持有时间

 

尽量缩短线程持有锁的时间,降低死锁发生的概率。可以将不需要加锁的操作放在锁的外部执行。

4. 避免嵌套锁

 

嵌套锁容易导致死锁,尽量减少嵌套锁的使用。如果必须使用嵌套锁,要确保锁的获取和释放顺序正确。
 
 
 
 
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示