MST
星途 面试题库

面试题:Go语言Mutex锁在复杂业务逻辑中的易错点探讨

假设我们正在开发一个多线程的银行转账系统,有一个`Account`结构体,包含`balance`(余额)字段和一个`Mutex`用于保护余额操作。以下是转账函数的代码片段: ```go package main import ( "fmt" "sync" ) type Account struct { balance int mu sync.Mutex } func (a *Account) transfer(to *Account, amount int) { a.mu.Lock() if a.balance >= amount { a.balance -= amount to.mu.Lock() to.balance += amount to.mu.Unlock() } a.mu.Unlock() } ``` 这段代码在使用Mutex锁时可能会出现什么严重问题?如何改进以确保转账操作的原子性和避免死锁?
28.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 问题
    • 死锁风险:这段代码存在死锁风险。当两个线程同时进行转账操作,例如线程A从账户X向账户Y转账,线程B从账户Y向账户X转账,线程A首先锁定账户X,线程B首先锁定账户Y,然后线程A尝试锁定账户Y,线程B尝试锁定账户X,此时就会发生死锁。
    • 原子性问题:虽然对单个账户的余额操作是线程安全的,但整个转账操作不是原子的。如果在a.balance -= amount之后,to.mu.Lock()之前发生了上下文切换,另一个线程可以读取到不一致的状态(即转出账户已扣钱,但转入账户还未加钱)。
  2. 改进方法
    • 避免死锁:可以通过对账户进行排序来避免死锁。总是先锁定序号小的账户,再锁定序号大的账户,这样就不会出现循环等待的情况。
    • 确保原子性:可以先获取两个账户的锁,然后再进行转账操作,这样能保证整个转账过程是原子的。

改进后的代码如下:

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
    }
}