面试题答案
一键面试设计场景
假设有一个银行转账的场景,多个线程可能同时尝试从一个账户向另一个账户转账。每个账户有一个余额,转账操作需要先锁定转出账户,再锁定转入账户,以确保余额的一致性。如果不妥善处理,可能会发生死锁,例如线程A锁定了账户1并等待账户2,而线程B锁定了账户2并等待账户1。
使用Tokio避免死锁
Tokio提供了tokio::sync::Mutex
来处理并发访问。为避免死锁,可以使用tokio::sync::MutexGuard
的自动释放特性,以及按照固定顺序锁定资源。
关键代码示例
use tokio::sync::Mutex;
// 定义账户结构体
struct Account {
balance: i32,
}
// 创建账户
let account1 = Mutex::new(Account { balance: 100 });
let account2 = Mutex::new(Account { balance: 200 });
// 转账函数
async fn transfer(
from: &Mutex<Account>,
to: &Mutex<Account>,
amount: i32,
) -> Result<(), String> {
// 按照相同顺序锁定账户
let mut from_guard = from.lock().await;
let mut to_guard = to.lock().await;
if from_guard.balance < amount {
return Err("Insufficient balance".to_string());
}
from_guard.balance -= amount;
to_guard.balance += amount;
Ok(())
}
代码逻辑解释
- 账户定义:
Account
结构体包含一个balance
字段,用于表示账户余额。 - 创建账户:使用
Mutex
包裹账户,确保并发安全。 - 转账函数:
transfer
函数接受两个Mutex
引用和转账金额。- 使用
from.lock().await
和to.lock().await
按照固定顺序锁定账户,避免死锁。lock
方法返回一个MutexGuard
,它会在作用域结束时自动释放锁。 - 检查转出账户余额是否足够,如果不足则返回错误。
- 完成转账操作,更新两个账户的余额。
通过这种方式,在多线程环境下使用Tokio的Mutex
可以有效避免死锁。