MST

星途 面试题库

面试题:Go语言内存模型对并发数据竞争的影响

结合Go语言的内存模型,阐述一下为什么在并发场景下,即使不同的goroutine操作不同的变量,也可能会出现数据竞争问题。如果有示例代码辅助说明更好。
17.0万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

在Go语言中,虽然不同的goroutine操作不同的变量在大多数直观场景下似乎不会产生数据竞争,但由于Go语言的内存模型特性,依然可能出现数据竞争问题,原因如下:

  1. 缓存一致性问题:现代CPU为了提高性能,每个核心都有自己的缓存。当一个goroutine在某个核心上修改了变量,这个修改可能暂时只存在于该核心的缓存中,其他核心的缓存还是旧值。如果另一个goroutine在其他核心上读取该变量,就可能读到旧值,造成数据不一致。
  2. 重排序优化:编译器和CPU为了优化性能,可能会对指令进行重排序。在单线程环境下,重排序不会影响程序逻辑,但在并发环境中,重排序可能导致不同goroutine之间观察到的操作顺序与代码编写顺序不一致,从而引发数据竞争。

示例代码如下:

package main

import (
    "fmt"
    "sync"
)

var (
    a int
    b int
)

func write() {
    a = 1
    b = 1
}

func read() {
    for {
        if b == 1 {
            fmt.Println(a)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        defer wg.Done()
        write()
    }()

    go func() {
        defer wg.Done()
        read()
    }()

    wg.Wait()
}

在上述代码中,write函数和read函数操作的是不同变量ab。按照正常逻辑,当read函数读到b == 1时,a也应该已经被赋值为1。但由于指令重排序,write函数中a = 1b = 1的执行顺序可能被改变,导致read函数读到b == 1时,a还未被赋值为1,从而打印出0,出现数据竞争问题。

为了避免这种数据竞争,在Go语言中通常使用sync.Mutexsync.RWMutex等同步机制,或者使用channel来进行数据通信和同步。例如:

package main

import (
    "fmt"
    "sync"
)

var (
    a int
    b int
    mu sync.Mutex
)

func write() {
    mu.Lock()
    a = 1
    b = 1
    mu.Unlock()
}

func read() {
    mu.Lock()
    if b == 1 {
        fmt.Println(a)
    }
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        defer wg.Done()
        write()
    }()

    go func() {
        defer wg.Done()
        read()
    }()

    wg.Wait()
}

使用sync.Mutex后,确保了writeread函数对共享变量的操作是原子性的,避免了数据竞争。