面试题答案
一键面试- 问题:
- 死锁风险:这段代码存在死锁风险。当两个线程同时进行转账操作,例如线程A从账户X向账户Y转账,线程B从账户Y向账户X转账,线程A首先锁定账户X,线程B首先锁定账户Y,然后线程A尝试锁定账户Y,线程B尝试锁定账户X,此时就会发生死锁。
- 原子性问题:虽然对单个账户的余额操作是线程安全的,但整个转账操作不是原子的。如果在
a.balance -= amount
之后,to.mu.Lock()
之前发生了上下文切换,另一个线程可以读取到不一致的状态(即转出账户已扣钱,但转入账户还未加钱)。
- 改进方法:
- 避免死锁:可以通过对账户进行排序来避免死锁。总是先锁定序号小的账户,再锁定序号大的账户,这样就不会出现循环等待的情况。
- 确保原子性:可以先获取两个账户的锁,然后再进行转账操作,这样能保证整个转账过程是原子的。
改进后的代码如下:
package main
import (
"fmt"
"sync"
"sort"
)
type Account struct {
balance int
mu sync.Mutex
}
func (a *Account) transfer(to *Account, amount int) {
var accounts []*Account
accounts = append(accounts, a, to)
sort.Slice(accounts, func(i, j int) bool {
return accounts[i] < accounts[j]
})
accounts[0].mu.Lock()
defer accounts[0].mu.Unlock()
accounts[1].mu.Lock()
defer accounts[1].mu.Unlock()
if a.balance >= amount {
a.balance -= amount
to.balance += amount
}
}