MST

星途 面试题库

面试题:Go中优化goroutine性能时如何避免常见的资源竞争问题

在优化goroutine性能的过程中,资源竞争是一个常见且棘手的问题。请说明在Go语言中,怎样通过互斥锁、读写锁、通道等机制来避免资源竞争,并且举例说明在复杂场景下如何合理运用这些机制来保证数据的一致性和提高性能。
21.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

互斥锁(Mutex)

互斥锁用于保证在同一时刻只有一个 goroutine 能够访问共享资源,从而避免资源竞争。

package main

import (
    "fmt"
    "sync"
)

var (
    counter int
    mu      sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    counter++
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final counter value:", counter)
}

在上述代码中,mu 是一个互斥锁,在访问和修改 counter 变量前,先通过 mu.Lock() 锁定,操作完成后通过 mu.Unlock() 解锁,确保同一时间只有一个 goroutine 能修改 counter

读写锁(RWMutex)

读写锁适用于读多写少的场景。它允许有多个读操作同时进行,但写操作时需要独占资源。

package main

import (
    "fmt"
    "sync"
)

var (
    data    int
    rwMutex sync.RWMutex
)

func read(wg *sync.WaitGroup) {
    defer wg.Done()
    rwMutex.RLock()
    fmt.Println("Read data:", data)
    rwMutex.RUnlock()
}

func write(wg *sync.WaitGroup, value int) {
    defer wg.Done()
    rwMutex.Lock()
    data = value
    fmt.Println("Write data:", data)
    rwMutex.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go read(&wg)
    }
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go write(&wg, i*10)
    }
    wg.Wait()
}

在这段代码中,读操作使用 rwMutex.RLock()rwMutex.RUnlock(),允许多个读操作并行;写操作使用 rwMutex.Lock()rwMutex.Unlock(),保证写操作时的独占性。

通道(Channel)

通道可以用于在 goroutine 之间传递数据,通过在通道上发送和接收数据来同步操作,避免直接访问共享资源。

package main

import (
    "fmt"
    "sync"
)

func producer(ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for value := range ch {
        fmt.Println("Consumed:", value)
    }
}

func main() {
    var wg sync.WaitGroup
    ch := make(chan int)
    wg.Add(1)
    go producer(ch, &wg)
    wg.Add(1)
    go consumer(ch, &wg)
    wg.Wait()
}

在这个例子中,producer goroutine 通过通道 chconsumer goroutine 发送数据,consumer 通过 for... range 从通道接收数据,直到通道关闭。这样避免了共享资源的竞争。

复杂场景下的运用

假设我们有一个银行账户的场景,需要支持存款、取款和查询余额操作,并且有多个并发的操作请求。

package main

import (
    "fmt"
    "sync"
)

type BankAccount struct {
    balance int
    mu      sync.RWMutex
}

func (a *BankAccount) Deposit(amount int) {
    a.mu.Lock()
    a.balance += amount
    a.mu.Unlock()
}

func (a *BankAccount) Withdraw(amount int) bool {
    a.mu.Lock()
    if a.balance >= amount {
        a.balance -= amount
        a.mu.Unlock()
        return true
    }
    a.mu.Unlock()
    return false
}

func (a *BankAccount) Balance() int {
    a.mu.RLock()
    balance := a.balance
    a.mu.RUnlock()
    return balance
}

func main() {
    account := BankAccount{}
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            account.Deposit(100)
        }()
    }

    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            account.Withdraw(50)
        }()
    }

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Println("Balance:", account.Balance())
        }()
    }

    wg.Wait()
}

在这个复杂场景中,对于存款和取款操作,因为涉及到对余额的修改,使用互斥锁 mu.Lock()mu.Unlock() 保证数据一致性;对于查询余额操作,因为只是读取数据,使用读写锁的读锁 mu.RLock()mu.RUnlock(),提高并发性能。通过这样合理运用互斥锁和读写锁机制,保证了银行账户操作在多 goroutine 并发情况下的数据一致性和性能。