Go 协程并发安全使用map

7,701 阅读1分钟

背景

go语言1.9之前map都是并发不安全的. 如果多个协程并发读写map.那么将会产生不可预知事件.造成很难排查的bug. 对系统很不好.

版本一,先看go 1.9之前并发读写不加锁
package main
func main() {
	m := make(map[int]int)
	go func1() { //协程1
		for {
			read := m[1]  并发读
			...
		}
	}()
	go func2() { //协程2
		for {
			m[2] = 2  // 并发写
		}
	}()

}

上面代码将会出现bug,甚至panic

加锁 解决并发安全问题

package main
func main() {
	m := make(map[int]int)
	mc := sync.Mutex
	go func1() { //协程1
		for {
		     mc.Lock()
			read := m[1]  并发读
			mc.Unlock()
			...
		}
	}()
	go func2() { //协程2
		for {
		     mc.Lock()
			m[2] = 2  // 并发写
			m.UnLock()
		}
	}()

}

经过上面每一步读写共享map前后加锁,解锁. 达到协程安全.

Go 1.9 以后官方给出了sync.Map 同步map. 其实内部也是基于锁,但是天生协程安全.并且性能还不错

sync.map就是1.9版本带的线程安全map,主要有:
主要
Store
LoadOrStore
Load
Delete
Range

在读取之前Load. 写入之前Store

package main
func main() {
	
	var m sync.Map
	go func1() { //协程1
		for {
		    // 无锁go 1.9 同步map 并发读
		     vv, ok = m.Load("1")
             fmt.Println(vv, ok) //one true
			
			...
		}
	}()
	go func2() { //协程2
		for {
		    // 并发写
			m.Store("1", "2") 
		}
	}()

}

同步map 注意还有dirty. range.

所以在store之前最好先读. 效率比之前的要好很多. 代码也简洁了. 建议大家使用. 另外如果需要删除所有map 只能 map = nil, make 新建map 这个时候记得一定要加锁!