MST

星途 面试题库

面试题:Go语言并发编程中的资源竞争问题及解决

在一个使用Go语言编写的高并发Web服务器程序中,多个协程可能同时访问和修改共享资源(例如全局的用户连接池),从而引发资源竞争问题。请详细阐述你会如何检测这种资源竞争,以及使用哪些技术手段(如互斥锁、读写锁、通道等)来解决该问题,最好结合示例代码说明。
36.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

检测资源竞争

Go语言提供了内置的竞态检测器,通过在编译和运行时添加 -race 标志来启用。例如,假设有如下代码:

package main

import (
    "fmt"
)

var counter int

func increment() {
    counter++
}

func main() {
    for i := 0; i < 10; i++ {
        go increment()
    }
    fmt.Println("Final counter:", counter)
}

运行命令 go run -race main.go,如果存在竞态条件,会输出类似如下信息:

==================
WARNING: DATA RACE
Write at 0x00c0000160a0 by goroutine 7:
  main.increment()
      /path/to/main.go:7 +0x2e

Previous read at 0x00c0000160a0 by main goroutine:
  main.main()
      /path/to/main.go:12 +0x6c

Goroutine 7 (running) created at:
  main.main()
      /path/to/main.go:10 +0x4e
==================
Final counter: 2
Found 1 data race(s)
exit status 66

解决资源竞争的技术手段

互斥锁(Mutex)

互斥锁用于保证在同一时刻只有一个协程可以访问共享资源。

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 < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final counter:", counter)
}

在这个例子中,mu.Lock() 确保只有一个协程可以进入临界区(counter++),mu.Unlock() 释放锁。

读写锁(RWMutex)

当读操作远多于写操作时,读写锁是更好的选择。读操作可以并发执行,写操作会独占资源。

package main

import (
    "fmt"
    "sync"
)

var (
    data  int
    rwmu  sync.RWMutex
)

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

func write(wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.Lock()
    data++
    fmt.Println("Write data:", data)
    rwmu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        if i%2 == 0 {
            wg.Add(1)
            go write(&wg)
        } else {
            wg.Add(1)
            go read(&wg)
        }
    }
    wg.Wait()
}

读操作使用 rwmu.RLock()rwmu.RUnlock(),写操作使用 rwmu.Lock()rwmu.Unlock()

通道(Channel)

通道可以用于在协程之间传递数据,从而避免直接共享资源。

package main

import (
    "fmt"
    "sync"
)

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

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

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

在这个例子中,生产者协程通过通道向消费者协程发送数据,而不是共享一个变量。这样可以避免资源竞争。