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技术站