Go语言中的map使用及并发安全

发布于:2024-05-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

首先,Go语言的map底层是哈希表,而C++map的底层是红黑树,C++unordered_map的底层才是哈希表。所以增删改查的时间复杂度都是O(1)。当我们使用的时候需要注意以下几点:

  1. map是引用类型,如果两个map同时指向一个底层,那么一个map的变动会影响到另一个map,这一点需要特殊注意。

  2. map的零值(Zero Value)是nil,对nil map进行任何添加元素的操作都会触发运行时错误(panic)。因此,使用必须先创建map,使用make函数,例如:

    func f(int n) {
        m := make([]map[int]int, n)
        for i := range m {
            m[i] = make(map[int]int) // m中的所有map在使用前必须初始化
        }
    }
    
  3. map的键可以是任何可以用==!=操作符比较的类型,比如字符串、整数、浮点数、复数和布尔类型等。但是,slice, map, function等类型不可以作为map的键。

  4. map在使用过程中不保证顺序遍历,每次遍历的结果可能会不同,这一点和C++unordered_map相似。

  5. map进行的所有操作,增删改都不是线程安全的,如果在一个goroutine中修改了map,同时在另一个goroutine中读取了map,可能会触发concurrent map read and map write的错误。

并发安全:

由上述的第五点可知,Go语言的map不是并发安全的。并发情况下,对于map的读写操作都需要加锁,或者会引起竞争条件甚至导致程序崩溃。为了在并发环境下使用map,我们可以使用sync包中的sync.RWMutex读写锁,或者使用sync.Map

比如,我们可以使用读写锁来保证多个goroutine都可以访问同一个map:

var m = make(map[string]int)
var mutex = &sync.RWMutex{}
// 基本使用流程与C++和Java中锁的流程类似
func write(key string, value int) {
    mutex.Lock()
    m[key] = value
    mutex.Unlock()
}

func read(key string) (int, bool) {
    mutex.Lock()
    defer mutex.RUnlock()
    value, ok := m[key]
    return value, ok
}

再比如,我们也可以使用sync.Map{}完成并发控制:

func f() {
	m := sync.Map{}

	list := []string{"A", "B", "C", "D"}

	wg := sync.WaitGroup{}
	// 添加协程,等待协程完成
	for i := 0; i < 20; i++ {
		// 启动20组协程
		wg.Add(1)
		// 添加协程数量,一次只启动一个
		go func() {
			defer wg.Done() // 协程结束之后 wg - 1
			for _, item := range list {
				// 存在则 + 1, 不存在则初始化为1
				value, ok := m.Load(item)
				if !ok {
					value, _ = m.LoadOrStore(item, 0)
				}
				val := value.(int) + 1
				m.Store(item, val)
			}
		}()
	}
	wg.Wait() // 等待所有协程完成
	m.Range(func(k, v any) bool {
		fmt.Println(k, v)
		return true
	})
	fmt.Println(m)
}

最后给大家推荐一个LinuxC/C++高级架构系统教程的学习资源与课程,可以帮助你有方向、更细致地学习C/C++后端开发,具体内容请见 https://xxetb.xetslk.com/s/1o04uB