MST

星途 面试题库

面试题:Go启动Goroutine资源竞争优化

假设你在一个Go程序中启动了多个Goroutine,它们会共享一些资源,从而产生资源竞争问题。请描述你会如何检测这种资源竞争,并给出优化启动Goroutine以避免资源竞争的具体代码示例及说明。
15.0万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

检测资源竞争

在Go中,可以使用-race标志来检测资源竞争。在编译和运行程序时带上该标志,Go运行时系统会记录所有的读写操作,并在检测到资源竞争时输出详细的报告。 例如:

go build -race
./your_binary

优化启动Goroutine以避免资源竞争

  1. 使用互斥锁(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 < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final counter value:", counter)
}
  • 说明:
    • counter是共享资源。
    • mu是一个sync.Mutex类型的互斥锁。
    • increment函数中,通过mu.Lock()获取锁,这样其他Goroutine在获取锁之前无法访问counter。操作完成后,通过mu.Unlock()释放锁,允许其他Goroutine获取锁并访问counter
  1. 使用读写锁(RWMutex)
    • 当读操作远远多于写操作时,读写锁可以提高性能。多个Goroutine可以同时进行读操作,但写操作需要独占访问。
    • 示例代码如下:
package main

import (
    "fmt"
    "sync"
)

var (
    data    int
    rwMutex sync.RWMutex
)

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

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

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go read(&wg)
    }
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go write(&wg)
    }
    wg.Wait()
}
  • 说明:
    • data是共享资源。
    • rwMutex是一个sync.RWMutex类型的读写锁。
    • read函数使用rwMutex.RLock()进行读锁定,允许多个Goroutine同时读。
    • write函数使用rwMutex.Lock()进行写锁定,确保同一时间只有一个Goroutine进行写操作。
  1. 使用通道(Channel)
    • 通过通道在Goroutine之间传递数据,而不是共享数据。
    • 示例代码如下:
package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Worker %d started job %d\n", id, j)
        result := j * 2
        fmt.Printf("Worker %d finished job %d, result: %d\n", id, j, result)
        results <- result
    }
}

func main() {
    const numJobs = 5
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    var wg sync.WaitGroup
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            worker(id, jobs, results)
        }(w)
    }

    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    go func() {
        wg.Wait()
        close(results)
    }()

    for r := range results {
        fmt.Println("Result:", r)
    }
}
  • 说明:
    • jobs通道用于向worker Goroutine发送任务。
    • results通道用于从worker Goroutine接收计算结果。
    • 通过通道传递数据,避免了多个Goroutine直接共享数据带来的资源竞争问题。