发现问题
今天在看代码的时候,遇见了多个协程写同一个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++ {
k := i // 必须使用局部变量
go func(idx int) {
a[idx] = 1
wg.Done()
}(k)
}
wg.Wait()
count := 0
for i := range a {
if a[i] != 0 {
count++
}
}
fmt.Println(count)
优点:无锁,不影响性能