MST
星途 面试题库

面试题:Go语言map在并发场景下的问题及解决方案

当在并发环境中使用Go语言的map时,会出现什么问题?请至少列举两种常见问题,并阐述相应的解决方案,包括但不限于使用`sync.Map`的场景和原理。
19.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

问题

  1. 数据竞争:Go语言的map不是线程安全的,多个goroutine同时读写map时会导致数据竞争,引发未定义行为,如程序崩溃或返回错误结果。例如多个goroutine同时向map中插入新键值对时,可能会破坏map的内部数据结构。
  2. 读取到脏数据:在并发写操作时,一个goroutine正在修改map,另一个goroutine读取该map,可能读取到部分修改后的数据,即脏数据,影响程序逻辑的正确性。

解决方案

  1. 使用sync.Mutex
    • 原理:通过互斥锁来保证同一时间只有一个goroutine能够访问map。在对map进行读写操作前,先获取锁,操作完成后释放锁。
    • 示例代码
package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex
var data = make(map[string]int)

func write(key string, value int, wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    data[key] = value
    mu.Unlock()
}

func read(key string, wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    fmt.Println(data[key])
    mu.Unlock()
}
  1. 使用sync.RWMutex
    • 原理:读写锁允许有多个读操作同时进行,但写操作时需要独占锁。读操作时获取读锁,写操作时获取写锁,适用于读多写少的场景。
    • 示例代码
package main

import (
    "fmt"
    "sync"
)

var mu sync.RWMutex
var data = make(map[string]int)

func write(key string, value int, wg *sync.WaitGroup) {
    defer wg.Done()
    mu.Lock()
    data[key] = value
    mu.Unlock()
}

func read(key string, wg *sync.WaitGroup) {
    defer wg.Done()
    mu.RLock()
    fmt.Println(data[key])
    mu.RUnlock()
}
  1. 使用sync.Map
    • 原理sync.Map内部采用了分段锁的机制,通过两个map(read和dirty)来优化读写性能。read map存储了大部分被频繁访问的数据,并且可以无锁读取;dirty map则存储了read map中没有的数据,写操作先更新dirty map,当dirty map中的数据被读取一定次数后,会将dirty map提升为read map。
    • 适用场景:适合高并发读写的场景,尤其是读操作频繁的情况。例如在分布式系统中存储一些缓存数据,多个节点并发读写这些缓存。
    • 示例代码
package main

import (
    "fmt"
    "sync"
)

var data sync.Map

func write(key string, value int, wg *sync.WaitGroup) {
    defer wg.Done()
    data.Store(key, value)
}

func read(key string, wg *sync.WaitGroup) {
    defer wg.Done()
    if value, ok := data.Load(key); ok {
        fmt.Println(value)
    }
}