go并发陷阱之map及sync.map

参考

https://www.jianshu.com/p/10a998089486

https://segmentfault.com/a/1190000010294041


map

在并发中直接使用map时,例如

func main() {
	var wg sync.WaitGroup
	ms := make(map[int]int)
	for i := 0; i < 10; i++ {
		wg.Add(1)
		n := i  //如果直接给go协程赋值i的话,因为go协程和主程序并发执行所以取到i值时,i已经变了。最后不会是0到9
		go func() {
			defer wg.Done()
			ms[n] = n
			}()
	}
	wg.Wait()
	fmt.Println(ms)
}

 只有极小的概率会打印出

map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9]

一般都会如下报错而退出程序

fatal error: concurrent map writes

 对于此,go官方博客有如下说明:

Maps are not safe for concurrent use: it's not defined what happens when you read and write to them simultaneously. If you need to read from and write to a map from concurrently executing goroutines, the accesses must be mediated by some kind of synchronization mechanism. One common way to protect maps is with sync.RWMutex.

并发使用map是不安全的。如果希望并发读或写map,必须提供某种同步机制,一般情况下通过读写锁sync.RWMutex实现对map的并发访问控制,代码如下:

func main() {
	var mu sync.RWMutex
	var wg sync.WaitGroup
	ms := make(map[int]int)
	for i := 0; i < 10; i++ {
		wg.Add(1)
		n := i
		go func() {
			defer wg.Done()
			mu.Lock()
			ms[n] = n
			mu.Unlock()
			}()
	}
	wg.Wait()
	fmt.Println(ms)
}

此时可以正常对map并发写操作,按照官方博客说法读操作也要加同步锁,但是经过屡次实操发现不加也正常执行,不会报错

func main() {
	var mu sync.RWMutex
	var wg sync.WaitGroup
	ms := make(map[int]int)
	for i := 0; i < 10; i++ {
		wg.Add(1)
		n := i
		go func() {
			defer wg.Done()
			mu.Lock()
			ms[n] = n
			mu.Unlock()
			}()
	}
	wg.Wait()
	fmt.Println(ms)
	for i := 0; i < 10; i++ {
		wg.Add(1)
		n := i
		go func() {
			defer wg.Done()
			fmt.Println(ms[n])
		}()
	}
	wg.Wait()
}

//output
//map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9]
//0
//1
//2
//3
//4
//5
//6
//7
//9
//8

 对于同时并发读写map,很明显读写操作都必须加同步锁,不然报错

fatal error: concurrent map read and map write

sync.map

sync.map类似于map,但是不必加锁就可以并发操作。然而大多数情况下还是应该使用map加锁。

sync.map更适用于对于一个key写一次但是读很多次这种场景,性能会比map加锁好。

sync.map的使用方法和性能分析,这篇文章已经讲的很好了https://segmentfault.com/a/1190000010294041

你可能感兴趣的:(go)