百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

C#中多线程的那点事-死锁(c#线程死锁的原因及解决方法)

bigegpt 2024-08-03 11:33 6 浏览



上一篇《C#中多线程的那点事-锁》,我们讲述了多线程编程过程中,一种限制多个线程对资源的同时访问的技术——锁。

小明同学,上周未和家人出去游玩去了。刚学了锁的用法,小明终于完善的模拟出了早餐店的流水线,所以他游玩很开心。但是回家的路上,却遇到了烦心事!

由于天气很好,小明一家人游玩到了天黑才驱车回家。正值交通拥堵的时候,在他们即将行进到一个环岛的时候,交通完全堵死了。

小明在车上看着道路资源被无限的占用着,联想到多线程编程中的锁:要是限制一下进入环岛的车辆的数量,是不是就不会出现这种无限的堵死在状态呢!

由于车辆太多,已经进入环岛的车辆,出环岛的路被堵死,无法出去,无法释放占用的道路资源。想要进入环岛的车辆,却又因为无法进入环岛而又一直占用着环岛的出口。这不就是个死循环嘛!

在堵了一个小时之后,小明一家人终于走过了这个环岛,不一会就到家了。

死锁演示

聪明的小明,直觉告诉他,在多线程中,也可能会出现这种现象。于是他用两个锁对象,模拟了环岛堵死的场景:

class Program
{

    static long uniqueRes = 0; // 唯一资源
    // 锁对象
    static object roadOut = new object();
    static object roadIn = new object();


    static void EnterCircle()
    {
        lock (roadIn)
        {
            // 模拟堵在了入口
            for (int i = 0; i < 10000; i++)
            {
                uniqueRes += 1;
            }

            lock (roadOut)
            {
                // 模拟堵在了出口
                for (int i = 0; i < 10000; i++)
                {
                    uniqueRes -= 1;
                }
            }
        }
    }
    static void ExitCircle()
    {
        // 将资源锁上
        lock (roadOut)
        {
            // 模拟堵在了出口处
            while (true)
            {
                uniqueRes -= 1;
            }
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Hello Thread Dead Locker!");

        var t1 = new Thread(EnterCircle);
        var t2 = new Thread(ExitCircle);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine(#34;sum: {uniqueRes}");
        Console.ReadKey();
    }
}

运行结果如下:


果然程序也堵住了。

资源交叉占用导致死锁

今天上课之前,小明向外老师讲述了他们家周末堵车的遭遇,还有他写的模拟程序无法结束的问题。

其实小明模拟的情形,是一种资源的恶意占用。一直占用资源而不归还,导致其他线程无法访问资源。这种情况,在实际编程中,不多见,除非是恶意代码。一般正常的代码,就算长时间占用资源,最终都会归还资源的。

但是有另外一种交叉占用资源导致的死锁问题,要特别小心。我把小明的代码做了一点修改:

class Program
{

    static long uniqueResA = 0; // 唯一资源  
    static long uniqueResB = 0; // 唯一资源
    // 锁对象
    static object A = new object();
    static object B = new object();
    static void Fun1()
    {
        lock (A)
        {
            for (int i = 0; i < 10000; i++)
            {
                uniqueResA += 1;
            }
            lock (B)
            {
                for (int i = 0; i < 10000; i++)
                {
                    uniqueResB -= 1;
                }
            }
        }
    }
    static void Fun2()
    {
        lock (B)
        {
            for (int i = 0; i < 10000; i++)
            {
                uniqueResB -= 1;
            }
            lock (A)
            {
                for (int i = 0; i < 10000; i++)
                {
                    uniqueResA += 1;
                }
            }
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Hello Thread Dead Locker!");

        var t1 = new Thread(Fun1);
        var t2 = new Thread(Fun2);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine(#34;A: {uniqueResA}  B: {uniqueResB}");
        Console.ReadKey();
    }
}


运行3次的结果如下:

由于Fun1和Fun2几乎同时执行,刚开始Fun1请求资源A成功,Fun2请求资源B也成功。但是当Fun1开始请求资源B的时候,如果Fun2还没有释放B,就会导致Fun1阻塞。Fun1阻塞又导致其无法归还资源A,进而导致Fun2又无法请求到资源A,进而又导致Fun2阻塞。。。



小明看到这个结果,就举手表示有疑问:为什么第三次执行成功了呢?

我并没有回答小明的问题,而是布置了作业:

1.同学们回家之后,将Fun1和Fun2中的循环中的计算的次数调高和调低,然后观察程序多次执行出现死锁的概率。

2.如何避免出现死锁现象?

3.如果出现死锁,如何解除死锁?

小明似乎明白了什么,但是我立即示意他不要说出来!先验证再说结论!

附:参考答案

1.循环中,计算次数越多,出现死锁的概率越高

2.a 尽量避免长时间占用资源

2.b 避免资源交叉请求

3.a 有序资源分配法

3.b 银行家算法


踩坑记录

这次发现一个和此前我的理解不同的地方,分享给大家:

请问开启4个线程,同时执行如下的DealRes函数,会导致死锁吗?

static long uniqueRes = 0; // 唯一资源
// 锁对象
static object locker = new object();

static void DealRes()
{
    // 将资源锁上
    lock (locker)
    {
        // 再来一次lock,会死锁吗?!
        lock (locker)
        {
            for (int i = 0; i < 10000; i++)
            {
                uniqueRes += 1;
            }
        }
    }
}


系列文章

下面是给同学们准备的干货:

C#中多线程的那点事儿-Thread入门

C#中多线程的那点事-多线程的代价

C#中多线程的那点事-线程池

C#中多线程的那点事-锁

C#中多线程的那点事-Task再次起航

C#中多线程的那点事-Parallel

C#中多线程的那点事-Linq & PLinq

C#中多线程的那点事-async & await

相关推荐

【机器学习】数据挖掘神器LightGBM详解(附代码)

来源:机器学习初学者本文约11000字,建议阅读20分钟本文为你介绍数据挖掘神器LightGBM。LightGBM是微软开发的boosting集成模型,和XGBoost一样是对GBDT...

3分钟,用DeepSeek全自动生成语音计算器,还带括号表达式!

最近,大家慢慢了解到了DeepSeek的强大功能,特别是它在编程领域也同样强大。编程零基础小白,一行代码不用写,也能全自动生成一个完整的、可运行的软件来!很多程序员一直不相信小白不写代码也能编软件!下...

python学习笔记 3.表达式

在Python中,表达式是由值、变量和运算符组成的组合。以下是一些常见的Python表达式:算术表达式:由数值和算术运算符组成的表达式,如加减乘除等。例如:5+3、7*2、10/3等。字符...

5.7 VS 8.x,为什么用户不升级MySql

一般来说为了更好的功能和性能,都需要将软件升级到最新的版本,然而在开源软件中,由于一些开发商变化或其他的问题(开源授权变化),致使人们不愿使用最新的版本,一个最典型的问题就是CentOS操作系统。还有...

大厂高频:讲一下MySQL主从复制

大家经常听说主从复制,那么主从复制的意义?能解决的问题有哪些?主从复制能解决的问题就是在我们平时开发的程序中操作数据库的时候,大多数的情况查询的操作大大超过了写的操作,也就说对数据库读取数据的压力比较...

MYSQL数据库的五大安全防护措施

以技术为基础的企业里最有价值的资产莫过于是客户或者其数据库中的产品信息了。因此,在这样的企业中,保证数据库免受外界攻击是数据库管理的重要环节。很多数据库管理员并没有实施什么数据库保护措施,只是因为觉得...

docker安装mysql

准备工作已安装Docker环境(官方安装文档)终端/命令行工具(Linux/macOS/WSL)步骤1:拉取MySQL镜像打开终端执行以下命令,拉取官方MySQL镜像(默认最新版本):d...

Zabbix监控系统系列之六:监控 mysql

zabbix监控mysql1、监控规划在创建监控项之前要尽量考虑清楚要监控什么,怎么监控,监控数据如何存储,监控数据如何展现,如何处理报警等。要进行监控的系统规划需要对Zabbix很了解,这里只是...

详解MySQL的配置文件及优化

#头条创作挑战赛#在Windows系统中,MySQL服务器启动时最先读取的是my.ini这个配置文件。在Linux系统中,配置文件为my.cnf,其路径一般为/etc/my.cnf或/etc/mysq...

Mysql 几个批处理执行脚本

学习mysql过程中,需要创建测试数据,并让多人每人一个数据库连接并进行作业检查。整合部分批处理创建数据批量创建数据库DELIMITER$CREATEPROCEDURECreateDatab...

MySQL学到什么程度?才有可以在简历上写精通

前言如今互联网行业用的最多就是MySQL,然而对于高级Web面试者,尤其对于寻找30k下工作的求职者,很多MySQL相关知识点基本都会涉及,如果面试中,你的相关知识答的模糊和不切要点,基...

mysql 主、从服务器配置“Slave_IO_Running: Connecting” 问题分析

#在进行mysql主、从服务器配置时,”SHOWSLAVESTATUS;“查看从库状态Slave_IO_Runing,出现错误:“Slave_IO_Running:Connectin...

MYSQL数据同步

java开发工程师在实际的开发经常会需要实现两台不同机器上的MySQL数据库的数据同步,要解决这个问题不难,无非就是mysql数据库的数据同步问题。但要看你是一次性的数据同步需求,还是定时数据同步,亦...

「MySQL 8」MySQL 5.7都即将停只维护了,是时候学习一波MySQL 8了

MySQL8新特性选择MySQL8的背景:MySQL5.6已经停止版本更新了,对于MySQL5.7版本,其将于2023年10月31日停止支持。后续官方将不再进行后续的代码维护。另外,...

Prometheus监控mysql

通过Prometheus监控Mysql,我们需要在Mysql端安装一个mysql-exporter,然后Prometheus通过mysql-exporter暴露的端口抓取数据。1.安装一个MYSQL配...