面试题答案
一键面试- 利用Mutex确保无数据竞争:
- 为每个账户结构体添加一个
sync.Mutex
字段。当进行账户余额的增减操作时,先锁定对应的Mutex
,操作完成后再解锁。这样在同一时间只有一个协程可以操作账户余额,避免数据竞争。
- 为每个账户结构体添加一个
- 避免死锁:
- 固定锁的获取顺序:如果转账操作涉及多个账户,为所有账户定义一个固定的顺序(比如按账户ID从小到大)。在进行转账时,所有协程都按照这个固定顺序获取锁。这样可以防止循环获取锁导致的死锁。
- 超时处理:在获取锁时使用
TryLock
或设置获取锁的超时时间。如果在规定时间内没有获取到锁,放弃当前操作并进行相应处理(比如重试或返回错误)。
以下是代码实现思路示例:
package main
import (
"fmt"
"sync"
)
// Account 定义账户结构体
type Account struct {
ID int
Balance int
mu sync.Mutex
}
// Transfer 转账函数
func Transfer(from, to *Account, amount int, wg *sync.WaitGroup) {
defer wg.Done()
// 固定锁的获取顺序,假设按账户ID从小到大
var first, second *Account
if from.ID < to.ID {
first, second = from, to
} else {
first, second = to, from
}
first.mu.Lock()
defer first.mu.Unlock()
second.mu.Lock()
defer second.mu.Unlock()
if from.Balance >= amount {
from.Balance -= amount
to.Balance += amount
} else {
fmt.Println("Insufficient balance in account", from.ID)
}
}
func main() {
var wg sync.WaitGroup
account1 := Account{ID: 1, Balance: 1000}
account2 := Account{ID: 2, Balance: 500}
// 模拟多个转账请求
for i := 0; i < 5; i++ {
wg.Add(1)
go Transfer(&account1, &account2, 100, &wg)
}
wg.Wait()
fmt.Println("Account 1 balance:", account1.Balance)
fmt.Println("Account 2 balance:", account2.Balance)
}
在上述代码中:
Account
结构体包含账户ID、余额和一个Mutex
。Transfer
函数在进行转账操作前,按账户ID的顺序获取锁,防止死锁。在获取锁后进行余额的增减操作,通过Mutex
保证数据的一致性,避免数据竞争。main
函数模拟了多个并发的转账请求。