首页 > 编程语言 >死锁(Deadlock)C#

死锁(Deadlock)C#

时间:2024-10-22 15:18:52浏览次数:7  
标签:Console Thread C# lock2 死锁 lock1 线程 Deadlock

        在多线程编程中,死锁(Deadlock)是一种非常常见的问题,通常发生在两个或多个线程相互等待对方持有的锁,导致它们都无法继续执行。要避免死锁,需要了解死锁的四个必要条件以及相应的解决策略。

死锁的形成

死锁是指两个或多个线程相互等待对方释放资源,导致所有线程都无法继续执行。典型的死锁场景如下:

1.线程1拥有资源A,并在等待资源B;

2.线程2拥有资源B,并在等待资源A;

3.两者互相等待对方释放资源,形成了一个循环依赖,导致所有线程都被永久阻塞。

死锁的四个必要条件:

1.互斥条件(Mutex Exclusion):资源一次只能被一个线程占用。

2.持有并等待条件(Hold and Wait):线程持有一个资源并等待获取其他资源。

3.不可剥夺条件(No Preemption):线程已获得的资源条件不能被强行剥夺,只能由线程自己释放。

4.循环等待条件(Circular Wait):存在一组线程,每个线程都在等待下一线程持有的资源,形成一个环形等待。

如果满足以上四个条件,死锁就有可能发生。因此,解决死锁的关键是打破这些条件之一。

避免死锁的策略

1.锁的顺序(Ordering Locks)

确保所有线程按相同的顺序请求锁。这可以打破死锁的循环等待条件。只要所有的线程都以相同的顺序请求资源,死锁就不会发生。

例如,假设有两个锁lock1和lock2,我们确保所有线程总是先获取lock1,然后后获取lock2,避免死锁。

示例:按顺序获取以避免死锁

private static readonly object _lock1 = new object();
private static readonly object _lock2 = new object();

public void Thread1Work()
{
    lock(_lock1)//线程1先获取锁1
    {
        Console.WriteLine("Thread 1 acquired lock1");

        Thread.Sleep(100);
        
        lock(_lock2)//线程1然后获取锁2
        {
            Console.WriteLine("Thread 1 acquired lock2");
        }
    }
}

public void Thread2Work()
{
    lock (_lock1)  // 线程2必须按相同顺序获取锁1
    {
        Console.WriteLine("Thread 2 acquired lock1");

        Thread.Sleep(100);

        lock (_lock2)  // 线程2然后获取锁2
        {
            Console.WriteLine("Thread 2 acquired lock2");
        }
    }
}

如何避免死锁:

•        锁的顺序:通过让线程按照相同的顺序获取锁,可以避免互相等待对方释放锁的问题。

•        结果:Thread1和Thread2都会先获取lock1,然后获取lock2,不会形成死锁。

2.锁的超时机制(Timeout)

使用超时机制来获取锁,如果某个线程在等待锁时超时,则可以放弃操作并避免死锁。这样可以打破持有并等待条件。

示例:使用超时检测潜在的死锁

private static readonly object _lock1 = new object();  // 锁对象1
private static readonly object _lock2 = new object();  // 锁对象2

public void Thread1Work()
{
    if (Monitor.TryEnter(_lock1, TimeSpan.FromSeconds(1)))  // 尝试获取锁1,超时时间1秒
    {
        try
        {
            Console.WriteLine("Thread 1 acquired lock1");

            // 模拟线程1需要额外的时间处理一些事情
            Thread.Sleep(100);

            if (Monitor.TryEnter(_lock2, TimeSpan.FromSeconds(1)))  // 尝试获取锁2,超时时间1秒
            {
                try
                {
                    Console.WriteLine("Thread 1 acquired lock2");
                }
                finally
                {
                    Monitor.Exit(_lock2);  // 释放锁2
                }
            }
            else
            {
                Console.WriteLine("Thread 1 failed to acquire lock2, potential deadlock detected.");
            }
        }
        finally
        {
            Monitor.Exit(_lock1);  // 释放锁1
        }
    }
    else
    {
        Console.WriteLine("Thread 1 failed to acquire lock1, potential deadlock detected.");
    }
}

public void Thread2Work()
{
    if (Monitor.TryEnter(_lock2, TimeSpan.FromSeconds(1)))  // 尝试获取锁2,超时时间1秒
    {
        try
        {
            Console.WriteLine("Thread 2 acquired lock2");

            // 模拟线程2需要额外的时间处理一些事情
            Thread.Sleep(100);

            if (Monitor.TryEnter(_lock1, TimeSpan.FromSeconds(1)))  // 尝试获取锁1,超时时间1秒
            {
                try
                {
                    Console.WriteLine("Thread 2 acquired lock1");
                }
                finally
                {
                    Monitor.Exit(_lock1);  // 释放锁1
                }
            }
            else
            {
                Console.WriteLine("Thread 2 failed to acquire lock1, potential deadlock detected.");
            }
        }
        finally
        {
            Monitor.Exit(_lock2);  // 释放锁2
        }
    }
    else
    {
        Console.WriteLine("Thread 2 failed to acquire lock2, potential deadlock detected.");
    }
}

如何避免死锁:

•        锁的超时机制:使用Monitor.TryEnter 来设置获取锁的超时时间。如果超过指定时间无法获取锁,线程可以退出或执行其他操作。

•        结果:如果一个线程未能在指定时间内获取锁,它将放弃尝试并避免陷入死锁。

3.减少锁的持有时间(Minimize Lock Scope)

尽量缩短线程持有锁的时间,减少锁的竞争,进而降低死锁的可能性。只在需要访问共享资源的最小范围内加锁,防止不必要的锁定。

示例:缩短锁的持有时间

private static readonly object _lock1 = new object();  // 锁对象1
private static int _sharedResource = 0;  // 共享资源

public void IncrementResource()
{
    // 仅在需要访问共享资源时获取锁
    lock (_lock1)
    {
        _sharedResource++;  // 修改共享资源
        Console.WriteLine($"Resource incremented to {_sharedResource} by Thread {Thread.CurrentThread.ManagedThreadId}");
    }
    // 锁在这里就释放了,不会在其他不必要的代码块中持有
    DoOtherWork();  // 其他操作不需要锁定
}

private void DoOtherWork()
{
    // 执行其他不需要锁定的任务
    Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is doing other work.");
}

如何避免死锁:

•        减少锁的持有时间:尽量缩小锁定的范围,确保只有在修改共享资源时才持有锁。这样可以减少锁竞争,降低死锁发生的机率。

•        结果:线程在修改完共享资源后立刻释放锁,从而使其他线程有机会获得锁。

总结:

死锁形成的条件:

1.互斥条件:每次只有一个线程能够访问资源。

2.持有并等待条件:线程已经持有一个资源,并在等待其他资源。

3.不可剥夺条件:线程持有的资源不能被强行剥夺,必须由线程自己释放。

4.循环等待条件:一组线程形成循环,每个线程都在等待下一个线程释放资源。

避免死锁的策略:

1.锁的顺序:确保所有线程按照相同的顺序获取锁,避免循环等待。

2.锁的超时机制:使用Monitor.TryEnter等等待超时的锁机制,避免无期限等待锁。

3.减少锁的持有时间:尽量缩小锁定范围,减少锁竞争,降低死锁的可能性。

标签:Console,Thread,C#,lock2,死锁,lock1,线程,Deadlock
From: https://blog.csdn.net/qq_73220363/article/details/143111833

相关文章

  • Docker 部署 JDK11 图文并茂简单易懂
    部署JDK11(Docker)[Step1]:下载JDK11-JDK11|Oracle甲骨文官网[Step2]:jdk11上传服务器/root/jdk11可自行创建文件夹进入目录/root/jdk11解压文件tar-zxvfjdk-11.0.22_linux-x64_bin.tar.gz解压后进入/root/jdk11/jdk-11.0.22创建jre文件......
  • Docker 部署 EMQX 一分钟极速部署
    部署EMQX(Docker)[Step1]:拉取EMQX镜像dockerpullemqx/emqx:latest[Step2]:创建目录➡️创建容器➡️拷贝文件➡️授权文件➡️删除容器#创建目录mkdir-p/data/emqx/{etc,data,log}#创建容器dockerrun-d--nameemqx-p1883:1883-p18083:18......
  • [DMY]CSP-S 模拟赛 Day 20
    CSP-S前最后一场代码源了。赛时T1看上去是一个很神秘的题目,在纸上推了半天勉勉强强想到一个奇怪的贪心做法。看到数据范围,发现直接做的话会超时,但是考虑到C++内置的sort函数可以帮助优化时间复杂度,所以写了个很丑的神秘排序。发现做完以后只能判断两种特殊情况,思考怎样......
  • JavaScript 函数定义
    JavaScript使用关键字 function 定义函数。函数可以通过声明定义,也可以是一个表达式。functionfunctionName(parameters){执行的代码}functionmyFunction(a,b){ returna*b;}函数表达式JavaScript函数可以通过一个表达式定义。函数表达式可以存储在变......
  • Webpack5-html
    处理Html资源1.下载包npmihtml-webpack-plugin-D2.配置webpack.config.jsconstpath=require("path");constHtmlWebpackPlugin=require("html-webpack-plugin");module.exports={entry:"./src/main.js",output:{p......
  • logback和日志分离管理
    目录一、日志框架1.什么是日志框架?2.日志框架的作用3.常见的日志框架3.1.Log4j3.2.Logback3.3.java.util.logging(JUL)3.4.SLF4J(日志门面)(SimpleLoggingFacadeforJava)4.常见的日志级别5.讲讲logback5.1.logback-core5.2.logback-classic5.3.logback-access5.4.学到......
  • Pycharm 的常用配置及快捷键,看这一篇就够了!
    在使用Pycharm编写代码时,掌握一些有必要的配置和快捷键操作,可以让我们的工作少走很多弯路~ 本篇文章我们会讲到:Pycharm设置主题设置菜单栏&代码字体、大小设置背景图修改注释颜色设置代码模版pycharm如何汉化pycharm常用快捷键一、Pycharm设置主题1、点击左上角的Fil......
  • 一文读懂什么是数据即产品(Data as a Product,DaaP)
    企业每天都要产生并消费大量数据,但如果这些数据一直保持在原始格式,就很难真正应用起来。因此,为了充分发挥数据的最大潜力,必须改变组织内部处理数据的方式。“数据即产品”(DaaP)就是这样一种思维方式转变的代表,即将原始数据转化为高质量的信息产品。这种转变不仅会改变企业的数据战......
  • Centos7二进制部署k8s
    前言:确保自己的配置正确,设计到很多文件相关配置导致不正确的就复制配置给ai编辑顺序在运行,报错有可能顺序出了问题,或者通过查看日志来观察哪里报错)一、部署etcd集群三台机器,所有机器相互做解析centos7.4关闭防⽕墙和selinux#sudosystemctlstopfirewalld#sudosete......
  • Linux系统上使用nmcli命令配置各种网络
    目录一、配置NetworkManager接管网络(选)安装Network-Manager并启动netplan管理网络的系统ifupdown管理网络的系统二、nmcli的相关配置(后置参数均可缩写)有线网络配置使用nmcli添加一个网卡并配置静态ip地址激活/关闭网卡使用nmcli修改一个网卡的地址使用nmcli添加一个网卡......