MST
星途 面试题库

面试题:Go协程中的并发安全问题探讨

在Go语言中使用协程进行并发编程时,常常会遇到并发安全问题。请举例说明一个可能出现并发安全问题的场景,并阐述如何使用`sync`包中的工具(如`sync.Mutex`、`sync.RWMutex`等)来解决该问题。
43.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

并发安全问题场景举例

假设我们有一个计数器,多个协程需要对其进行加一操作。如果不做任何并发控制,就会出现数据竞争,导致最终的计数值不准确。以下是示例代码:

package main

import (
    "fmt"
)

var count int

func increment() {
    count = count + 1
}

func main() {
    var numRoutines = 1000
    for i := 0; i < numRoutines; i++ {
        go increment()
    }
    // 这里没有合适的等待机制,可能主函数先退出,协程未执行完
    fmt.Println("Final count:", count)
}

在上述代码中,由于多个协程同时对count变量进行读写操作,会导致数据竞争,每次运行程序,count的最终值可能都不一样,且通常小于1000。

使用sync.Mutex解决问题

我们可以使用sync.Mutex来保护对count变量的读写操作,确保同一时间只有一个协程能访问它。修改后的代码如下:

package main

import (
    "fmt"
    "sync"
)

var (
    count int
    mu    sync.Mutex
)

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

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

在这个版本中,mu.Lock()mu.Unlock()之间的代码块是临界区,同一时间只有一个协程可以进入,从而保证了count变量的读写操作的安全性。

使用sync.RWMutex解决问题

如果读操作远远多于写操作,可以使用sync.RWMutexsync.RWMutex允许多个协程同时进行读操作,但写操作时会独占锁,防止其他读写操作。假设我们有一个场景,除了上述的写操作(increment函数),还有一个读操作函数getCount。示例代码如下:

package main

import (
    "fmt"
    "sync"
)

var (
    count int
    mu    sync.RWMutex
)

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

func getCount() int {
    mu.RLock()
    defer mu.RUnlock()
    return count
}

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

getCount函数中,使用mu.RLock()mu.RUnlock()来进行读锁定,允许多个协程同时读。而在increment函数中,仍然使用mu.Lock()mu.Unlock()进行写锁定,保证写操作的原子性。