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