MST

星途 面试题库

面试题:Go语言如何在并发环境下安全使用map

在Go语言的并发编程场景中,描述至少两种使map安全并发访问的方法,并分别阐述每种方法的优缺点,同时给出简单的代码示例。
36.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

方法一:使用sync.Mutex

  1. 原理:通过sync.Mutex来保护对map的读写操作,在进行读写前加锁,操作完成后解锁,从而避免并发冲突。
  2. 优点
    • 实现简单,易于理解和使用。
    • 可以灵活控制锁的粒度,比如只对关键的读写操作加锁。
  3. 缺点
    • 性能问题,在高并发场景下,频繁的加锁和解锁会带来额外的开销,降低程序的并发性能。
    • 可能会导致死锁,如果锁的使用不当。
  4. 代码示例
package main

import (
    "fmt"
    "sync"
)

var (
    mu    sync.Mutex
    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()
    value := data[key]
    fmt.Printf("Read key %s, value %d\n", key, value)
    mu.Unlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go write("key1", 1, &wg)
    go read("key1", &wg)
    wg.Wait()
}

方法二:使用sync.RWMutex

  1. 原理sync.RWMutex是读写锁,允许多个读操作并发执行,但写操作时会独占锁,不允许其他读写操作。读锁和写锁相互排斥,写锁优先级更高。
  2. 优点
    • 在读多写少的场景下性能较好,因为读操作可以并发进行,提高了并发读的效率。
    • 相比于sync.Mutex,减少了读操作之间的竞争。
  3. 缺点
    • 实现相对复杂一些,需要区分读锁和写锁的使用场景。
    • 写操作仍然是独占的,在写操作频繁时,性能提升有限。
  4. 代码示例
package main

import (
    "fmt"
    "sync"
)

var (
    rwmu  sync.RWMutex
    rwdata = make(map[string]int)
)

func rwrite(key string, value int, wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.Lock()
    rwdata[key] = value
    rwmu.Unlock()
}

func rread(key string, wg *sync.WaitGroup) {
    defer wg.Done()
    rwmu.RLock()
    value := rwdata[key]
    fmt.Printf("Read key %s, value %d\n", key, value)
    rwmu.RUnlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go rwrite("key1", 1, &wg)
    go rread("key1", &wg)
    wg.Wait()
}

方法三:使用sync.Map

  1. 原理sync.Map是Go 1.9 引入的专门用于并发场景的map实现,它内部使用了多个map和互斥锁来实现高效的并发访问。
  2. 优点
    • 无需手动加锁,使用方便,直接调用其方法即可安全地进行并发读写操作。
    • 性能较高,在高并发场景下,比手动加锁的map性能更好,尤其是在读写操作频繁且无规律的情况下。
  3. 缺点
    • 不支持len方法获取元素数量,不能像普通map一样方便地遍历。
    • 相比于普通map,内存占用可能会稍高。
  4. 代码示例
package main

import (
    "fmt"
    "sync"
)

func main() {
    var sm sync.Map
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        sm.Store("key1", 1)
    }()
    go func() {
        defer wg.Done()
        value, ok := sm.Load("key1")
        if ok {
            fmt.Printf("Read key key1, value %d\n", value)
        }
    }()
    wg.Wait()
}