MST

星途 面试题库

面试题:Go闭包在复杂数据封装与并发场景下的考量

在一个多并发的系统中,有一个 `Account` 结构体表示账户,包含余额 `Balance` 字段。使用闭包封装对 `Balance` 的操作(如存款、取款),确保在并发环境下数据的一致性和安全性,同时说明如何处理可能出现的竞态条件,给出完整代码和详细解释。
28.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
package main

import (
    "fmt"
    "sync"
)

// Account 结构体表示账户
type Account struct {
    Balance int
    mu      sync.Mutex
}

// NewAccount 创建一个新账户并返回包含操作闭包的结构体
func NewAccount(initialBalance int) struct {
    Deposit  func(int)
    Withdraw func(int)
    GetBalance func() int
} {
    account := Account{
        Balance: initialBalance,
    }

    deposit := func(amount int) {
        account.mu.Lock()
        defer account.mu.Unlock()
        account.Balance += amount
    }

    withdraw := func(amount int) {
        account.mu.Lock()
        defer account.mu.Unlock()
        if account.Balance >= amount {
            account.Balance -= amount
        } else {
            fmt.Println("余额不足")
        }
    }

    getBalance := func() int {
        account.mu.Lock()
        defer account.mu.Unlock()
        return account.Balance
    }

    return struct {
        Deposit  func(int)
        Withdraw func(int)
        GetBalance func() int
    }{
        Deposit:  deposit,
        Withdraw: withdraw,
        GetBalance: getBalance,
    }
}

func main() {
    var wg sync.WaitGroup
    account := NewAccount(100)

    // 模拟并发存款
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            account.Deposit(10)
        }()
    }

    // 模拟并发取款
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            account.Withdraw(20)
        }()
    }

    wg.Wait()
    fmt.Println("最终余额:", account.GetBalance())
}

代码解释

  1. Account 结构体:定义了 Account 结构体,包含 Balance 字段用于表示账户余额,以及一个 sync.Mutex 类型的 mu 字段用于互斥锁,以保护对 Balance 的并发访问。
  2. NewAccount 函数:此函数接受一个初始余额作为参数,返回一个包含存款 Deposit、取款 Withdraw 和获取余额 GetBalance 操作的闭包的结构体。
    • Deposit 闭包:使用 account.mu.Lock() 锁定互斥锁,确保在存款操作执行期间,其他协程无法访问 Balance 字段。操作完成后,使用 defer account.mu.Unlock() 解锁互斥锁。存款操作只是简单地将传入的金额加到 Balance 上。
    • Withdraw 闭包:同样先锁定互斥锁,检查余额是否足够。如果足够则减去取款金额,否则打印余额不足的提示。最后解锁互斥锁。
    • GetBalance 闭包:锁定互斥锁,返回当前的 Balance 值,然后解锁互斥锁。
  3. main 函数:创建一个初始余额为 100 的账户,并模拟多个协程并发执行存款和取款操作。使用 sync.WaitGroup 等待所有协程完成操作,最后打印最终的账户余额。

竞态条件处理

在这个实现中,通过使用 sync.Mutex 互斥锁来处理竞态条件。在对 Balance 字段进行任何读或写操作之前,先锁定互斥锁,操作完成后解锁。这样可以确保在同一时间只有一个协程能够访问和修改 Balance 字段,从而保证了数据的一致性和安全性。如果不使用互斥锁,多个协程同时读写 Balance 字段可能会导致数据不一致的问题,例如丢失更新或者读取到脏数据。