发现问题
今天在看代码的时候,遇见了多个协程写同一个slice的情况,发现未对slice做任何保护,亦未使用其他手段保证并发安全,这样肯定会出错的。
思考
slice不是协程安全的,所以在多个协程中读写slice是不安全的,在高并发的情况下会产生不可控制的错误。
总结
这里记录一下错误的使用方式与正确的使用方式:
错误的使用方式:
1var a []int 2 3for i := 0; i < 10000; i++ { 4 go func() { 5 a = append(a, 1) // 多协程并发读写slice 6 }() 7} 8 9fmt.Println(len(a))
输出结果可能不等于期望的值
正确对方式
第一种方式:
对slice加锁,进行保护
1 num := 10000 2 3 var a []int 4 var l sync.Mutex 5 6 var wg sync.WaitGroup 7 wg.Add(num) 8 9 for i := 0; i < num; i++ { 10 go func() { 11 l.Lock() // 加锁 12 a = append(a, 1) 13 l.Unlock() // 解锁 14 wg.Done() 15 }() 16 } 17 18 wg.Wait() 19 20 fmt.Println(len(a))
缺点:锁会影响性能
第二种方式:
使用channel的传递数据
1num := 10000 2 3 var wg sync.WaitGroup 4 wg.Add(num) 5 6 c := make(chan int) 7 for i := 0; i < num; i++ { 8 go func() { 9 c <- 1 // channl是协程安全的 10 wg.Done() 11 }() 12 } 13 14 // 等待关闭channel 15 go func() { 16 wg.Wait() 17 close(c) 18 }() 19 20 // 读取数据 21 var a []int 22 for i := range c { 23 a = append(a, i) 24 } 25 26 fmt.Println(len(a))
第三种方式:
使用索引
1num := 10000 2 3 a := make([]int, num, num) 4 5 var wg sync.WaitGroup 6 wg.Add(num) 7 8 for i := 0; i < num; i++ { 9 k := i // 必须使用局部变量 10 go func(idx int) { 11 a[idx] = 1 12 wg.Done() 13 }(k) 14 } 15 16 wg.Wait() 17 18 count := 0 19 for i := range a { 20 if a[i] != 0 { 21 count++ 22 } 23 } 24 fmt.Println(count)
优点:无锁,不影响性能