C#多线程死锁介绍与案例代码

C#多线程死锁介绍与案例代码

死锁的概念

死锁(Deadlock)指的是多个线程因相互等待而陷入的一种僵局,每个线程都在等待其他线程释放资源。因此,所有线程都处于无法继续执行的状态,形成了死锁。

死锁产生的原因

死锁是由于多个线程相互等待对方所占用的资源而产生的。举例来说,有两个线程 A 和 B,他们需要占用相互持有的两个资源 R1 和 R2,但由于占用资源 R1 和 R2 的顺序不同,导致双方都在等待对方释放已经占用的资源,最终发生死锁。

死锁案例代码

下面是一个简单的死锁案例代码:

public class Account
{
    private readonly object _lockObject = new object();
    public int Balance { get; private set; }

    public void Deposit(int amount)
    {
        lock (_lockObject)
        {
            Balance += amount;
        }
    }

    public void Withdraw(int amount)
    {
        lock (_lockObject)
        {
            Balance -= amount;
        }
    }

    public void Transfer(Account destination, int amount)
    {
        lock (_lockObject)
        {
            destination.Deposit(amount);
            Withdraw(amount);
        }
    }
}

public class Test
{
    public static void Main()
    {
        Account account1 = new Account();
        account1.Deposit(100);

        Account account2 = new Account();
        account2.Deposit(100);

        Thread thread1 = new Thread(() =>
        {
            account1.Transfer(account2, 50);
        });

        Thread thread2 = new Thread(() =>
        {
            account2.Transfer(account1, 50);
        });

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Console.WriteLine("Account 1 Balance: " + account1.Balance);
        Console.WriteLine("Account 2 Balance: " + account2.Balance);
    }
}

在上述代码中,Account 类代表一个简单的银行账户,它有三个方法 Deposit、Withdraw 和 Transfer。其中,Deposit 和 Withdraw 方法用于分别向账户中存入和取出一定金额的钱,而 Transfer 方法用于从当前账户向目标账户转账。

而 Test 类的 Main 方法则创建了两个账户 account1 和 account2,每个账户分别存入了 100 元。

在创建两个线程 thread1 和 thread2 后,它们分别开始了钱的转账操作,其中 thread1 先将 50 元从 account1 转账到 account2 上,而 thread2 则将 50 元从 account2 转账到 account1 上。

然而,由于 Transfer 方法中先对目标账户进行锁定并执行 Deposit 操作后,才对当前账户进行锁定并执行 Withdraw 操作,因此在两个线程同时执行时,就会出现死锁的情况:假设 thread1 先占用了 account1 的锁,而 thread2 先占用了 account2 的锁,在互相等待对方释放锁的过程中,两个线程都无法继续执行,形成了死锁。

死锁的避免

避免死锁的方法主要有以下几种:

  • 按固定的顺序获取锁:通过约定一定的顺序,在多个线程中按照约定的顺序获取锁和释放锁,可以避免死锁的产生。
  • 使用内置锁:在 C# 中,使用 lock 关键字可以获得内置锁,它能够自动检测并在需要时等待获取锁,释放锁需要显式地调用 lock 代码块。
  • 使用超时机制:在获取锁时指定一个超时时间,在超时时间内无法获取到锁则进行回退,避免长时间等待而产生死锁。

示例说明

下面介绍一些死锁案例代码中避免死锁的实现方式:

按固定的顺序获取锁

通过定义账户的 ID,按照升序对他们进行排序,然后按照排序后的顺序获取锁和释放锁,可以有效避免死锁:

public class Account
{
    private readonly object _lockObject = new object();

    public int Id { get; private set; }
    public int Balance { get; private set; }

    public Account(int id, int balance)
    {
        Id = id;
        Balance = balance;
    }

    public void Deposit(int amount)
    {
        lock (_lockObject)
        {
            Balance += amount;
        }
    }

    public void Withdraw(int amount)
    {
        lock (_lockObject)
        {
            Balance -= amount;
        }
    }

    public void Transfer(Account destination, int amount)
    {
        if (Id < destination.Id)
        {
            lock (_lockObject)
            {
                lock (destination._lockObject)
                {
                    destination.Deposit(amount);
                    Withdraw(amount);
                }
            }
        }
        else
        {
            lock (destination._lockObject)
            {
                lock (_lockObject)
                {
                    Withdraw(amount);
                    destination.Deposit(amount);
                }
            }
        }
    }
}

在 Transfer 方法中,首先比较源账户和目标账户的 ID,如果源账户的 ID 小于目标账户的 ID,则先对源账户进行锁定再对目标账户进行锁定,否则就先对目标账户进行锁定再对源账户进行锁定。这种方式可以避免死锁的发生。

使用内置锁

使用 C# 中的 lock 关键字可以获得内置锁,它能够自动检测并在需要时等待获取锁,释放锁需要显式地调用 lock 代码块:

public class Account
{
    public int Balance { get; private set; }

    public void Deposit(int amount)
    {
        lock (this)
        {
            Balance += amount;
        }
    }

    public void Withdraw(int amount)
    {
        lock (this)
        {
            Balance -= amount;
        }
    }

    public void Transfer(Account destination, int amount)
    {
        lock (this)
        {
            lock (destination)
            {
                destination.Deposit(amount);
                Withdraw(amount);
            }
        }
    }
}

在 Transfer 方法中,对源账户和目标账户都使用 lock 关键字进行锁定,这样就能够保证在多个线程中不会出现竞争而导致死锁。

总之,避免死锁一直都是多线程编程中需要深入思考和处理的问题,开发人员需要注意锁的力度、锁的获取顺序、锁的防护范围等多个方面,从而避免死锁的发生。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#多线程死锁介绍与案例代码 - Python技术站

(0)
上一篇 2023年5月31日
下一篇 2023年5月31日

相关文章

  • C# Linq的Min()方法 – 返回序列中的最小值

    C#的Linq是一种强大的数据查询和操作工具,可以让程序员更加高效地使用各种数据源,其中Min()函数是Linq中常用的一个函数,接下来我们就来一步步讲解如何使用Min()函数。 Min()函数的简介 Min()是Linq中的一个聚合函数(Aggregation Function),可以用来在一组数据中找到最小值并返回。它可以用于各种类型的数据,包括整数、浮…

    C# 2023年4月19日
    00
  • c#和javascript函数相互调用示例分享

    下面我将为您详细讲解“C#和JavaScript函数相互调用示例分享”的完整攻略。本篇攻略共包含以下内容: 前言 C#函数调用JavaScript函数示例 JavaScript函数调用C#函数示例 总结 前言 在Web开发中,我们常常需要在C#和JavaScript之间相互调用函数。下面,我将分别为您介绍C#函数调用JavaScript函数示例和JavaSc…

    C# 2023年5月15日
    00
  • C#字符串数组转换为整形数组的方法

    以下是详细的讲解“C#字符串数组转换为整形数组的方法”的攻略: 方法一:使用循环遍历 首先,我们可以使用for循环遍历字符串数组,然后逐一转换成整型,保存至目标整型数组中。 string[] strArray = {"10", "20", "30"}; int[] intArray = new in…

    C# 2023年6月7日
    00
  • C#中获取二维数组的行数和列数以及多维数组各个维度的长度

    获取二维数组的行数和列数可以通过以下两种方式实现。 第一种方法是使用数组的Length和GetLength方法来获取。其中,数组的Length属性可以得到数组元素的总数量,而GetLength方法可以获得指定维度的元素数。 以下是示例代码: int[,] arr2D = new int[3, 4]; int row = arr2D.GetLength(0);…

    C# 2023年6月6日
    00
  • 在C# 8中如何使用默认接口方法详解

    当在一个现有的接口中添加新的成员时,会面临着兼容性问题,因为所有使用该接口的实现类都需要进行相应的修改。针对这种情况,C# 8推出了接口的默认实现方法的特性。通过默认实现方法,接口的作者可以为接口提供新功能,而无需破坏面向对象设计中的接口整体抽象性原则。 一、默认接口方法的定义 默认接口方法的定义与普通接口方法一致,不同的在于将其实现体嵌入在接口定义之中,并…

    C# 2023年6月6日
    00
  • asp.net AutoCompleteExtender的一个简单例子代码

    让我们来详细讲解“asp.net AutoCompleteExtender的一个简单例子代码”的完整攻略。 概述 AutoCompleteExtender是ASP.NET AJAX库的一个控件,可以帮助实现输入框的“自动补全”功能,可方便地进行基于 AJAX 技术的实时搜索,并返回搜索结果。它可以很方便地增强用户的输入体验,提高某些场景下的用户体验。 下面我…

    C# 2023年5月31日
    00
  • C#实现对AES加密和解密的方法

    首先,C#实现对AES加密和解密需要使用 System.Security.Cryptography 命名空间中提供的 Aes 类。下面是具体的实现步骤: 1. 导入命名空间 using System.Security.Cryptography; 2. 创建 Aes 对象 Aes aes = Aes.Create(); 3. 设置密钥和向量 密钥和向量是 AE…

    C# 2023年6月8日
    00
  • C# Linq的Select()方法 – 将序列中的每个元素投影到新形式中

    C# Linq中的Select()是一个用于在查询中选择特定数据,提取它们并创建新的数据结构的方法。该方法可以将集合、列表、数组等多种数据类型中的数据进行选择、投影、转换和过滤,在实际应用中非常实用。下面是详细讲解C#Linq的Select()的完整攻略: 一、Select()简介 Select()方法是Linq中最常用的方法之一,用于对序列中的每个元素应用…

    C# 2023年4月19日
    00
合作推广
合作推广
分享本页
返回顶部