MST

星途 面试题库

面试题:Go中常见的因共享变量导致的并发编程陷阱及解决办法

在Go语言的并发编程中,由于多个goroutine可能同时访问共享变量,常引发一些问题。请阐述一种因共享变量导致的并发编程陷阱,并说明如何使用Go语言提供的机制(如互斥锁等)来解决这个问题。
28.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

并发编程陷阱 - 竞态条件

  1. 问题阐述
    • 当多个goroutine同时读写共享变量时,就可能出现竞态条件。例如,有一个共享变量count,多个goroutine都对其进行加1操作。如果没有适当的同步机制,由于CPU调度的不确定性,可能会导致结果不正确。比如,两个goroutine同时读取count的值为10,然后各自加1后写回,最终count的值为11而不是预期的12。
  2. 解决方案 - 使用互斥锁(Mutex)
    • 在Go语言中,可以使用sync.Mutex来解决竞态条件问题。以下是示例代码:
package main

import (
    "fmt"
    "sync"
)

var (
    count int
    mu    sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    count++
    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 count:", count)
}
  • 在上述代码中,sync.Mutex类型的变量mu被定义。在increment函数中,通过mu.Lock()获取锁,这样在同一时间只有一个goroutine能够进入临界区(即对count进行操作的代码段),操作完成后通过mu.Unlock()释放锁,从而避免了竞态条件。

另一种解决方案 - 使用读写锁(RWMutex)

  1. 适用场景
    • 当有大量的读操作和少量的写操作时,使用读写锁(sync.RWMutex)会更加高效。读操作可以同时进行,而写操作需要独占锁。
  2. 示例代码
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++ {
        wg.Add(1)
        go read(&wg)
    }
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go write(&wg)
    }
    wg.Wait()
}
  • 在这个例子中,读操作使用rwmu.RLock()获取读锁,可以多个读操作同时进行;写操作使用rwmu.Lock()获取写锁,此时会阻止其他读写操作,保证数据的一致性。