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日

相关文章

  • .NET通过字典给类赋值实现代码

    对于.NET Framework提供的某些类型,我们可以通过字典的方式给类对象中的属性赋值。下面是实现过程的完整攻略: 1. 引入命名空间 在使用字典给类赋值时,我们需要引入System.Reflection命名空间。在代码中添加以下语句即可: using System.Reflection; 2. 创建类对象 首先,我们需要创建类的对象,以便我们可以给类的…

    C# 2023年5月31日
    00
  • c# 静态类的使用场景

    下文是关于”C# 静态类的使用场景”的完整攻略。 什么是 C# 静态类 在 C# 中,静态类(Static Class)是指不能被实例化的类,该类中的所有成员都必须是静态的。同时,静态类不能被继承,因此它不能有实例。静态类通常用于封装工具方法,使这些方法可以作为应用程序的公用工具使用。 C# 静态类的使用场景 静态类的主要作用是封装公用的工具方法,以方便其他…

    C# 2023年5月31日
    00
  • C# dump系统lsass内存和sam注册表详细

    首先我们需要了解一下“C#dump系统lsass内存和sam注册表详细”是什么。 lsass.exe是Windows系统的一个进程,它主要负责对用户登录信息进行验证和授权。lsass.exe在Windows系统启动时自动运行,并且至关重要。SAM(Security Accounts Manager)注册表是Windows系统中的一个数据库,其中存储着用户名和…

    C# 2023年5月15日
    00
  • asp.net DropDownList自定义控件,让你的分类更清晰

    下面我将详细讲解“asp.net DropDownList自定义控件,让你的分类更清晰”的攻略,以下是完整的步骤: 第一步:新建自定义控件 在Visual Studio中,新建一个类库项目,命名为“CustomDropDownList”。右键该项目,选择“添加”->“新建项”->“Web”->“Web用户控件”,并将其命名为“CustomD…

    C# 2023年5月31日
    00
  • C# FileAttributes.ReadOnly:表示文件或目录为只读文件或目录

    FileAttributes.ReadOnly 是一个枚举值,表示文件或文件夹是否为只读文件或文件夹。它主要用于设置或获取文件或文件夹的只读属性。 使用 FileAttributes.ReadOnly 可以帮助我们保护某些重要的文件或文件夹,避免它们被意外的修改或删除。 下面是对使用 FileAttributes.ReadOnly 的完整攻略: 1. 获取文…

    C# 2023年4月19日
    00
  • js无刷新操作table的行和列

    操作table的行 要实现js无刷新操作table的行,我们可以通过以下方式: 找到对应的<tr>元素,使用DOM API进行操作 或者通过ajax向后端发送请求,返回表格的新数据,再用js更新表格的内容 以下是一个示例代码,实现通过点击按钮删除特定的一行: <table> <tr> <td>行1-列1<…

    C# 2023年6月1日
    00
  • C#编程获取客户端计算机硬件及系统信息功能示例

    下面是详细讲解“C#编程获取客户端计算机硬件及系统信息功能示例”的完整攻略: 介绍 在开发过程中,我们有时需要获取客户端计算机的硬件和系统信息来帮助我们更好地处理业务逻辑。这个过程可以用C#编程来实现,这篇攻略将介绍如何通过C#获取客户端计算机的硬件和系统信息。 获取硬件信息 要获取客户端计算机的硬件信息,可以使用System.Management命名空间中…

    C# 2023年6月7日
    00
  • C#操作Windows服务类System.ServiceProcess.ServiceBase

    C#操作Windows服务需要使用System.ServiceProcess.ServiceBase类。下面是使用这个类的完整攻略。 ServiceBase类 ServiceBase类是用于开发Windows服务的基类,它提供了操作Windows服务的方法和属性。 安装/卸载服务 安装Windows服务需要使用InstallUtil.exe工具,在Visua…

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