并发安全也叫线程安全,在并发中出现了数据的丢失,称为并发不安全
map和slice都是并发不安全的,我们这次先看看切片的情况。
看下面demo:
package main
import (
"fmt"
"sync"
)
var s []int
func appendValue(i int) {
s = append(s, i)
}
func main() {
for i := 0; i < 10; i++ {
go appendValue(i)
}
for i, v := range s { //同时打印索引和值
fmt.Println(i, ":", v)
}
}
开启10个协程,每个协程对切片s追加值。运行代码会发现每次结果会不一样,但是多数情况切片的总数是小于10条的。比如下面:
D:\workspace\go\src\test>go run main.go
0 : 2
1 : 0
2 : 1
3 : 5
4 : 3
5 : 4
6 : 6
7 : 8
8 : 9
原因个人觉得有两种,而且两种原因都在上面demo都会出现:
第一种:当第一个for循环结束之后,开启的10个协程同时对切片s追加值,此时第二个for循环执行了,还没有等到10个协程全部追加完,第二个for就结束了【主协程提前结束】,切片s就是小于10个的情况。
第二种:协程并行 它们append时候的索引都一样 导致后者可能会覆盖前者append的值,就是同一个时刻多个协程同时追加,可是这个时候切片的下标只有一个,那只能覆盖了,比如协程5和6都选择下标是4的,那只能有一个选择成功。
还有网上很多文章说加锁就能解决,但是我试了一下并不是,代码如下:
package main
import (
"fmt"
"sync"
)
var s []int
var lock sync.Mutex //互斥锁
func appendValue(i int) {
lock.Lock() //加锁
s = append(s, i)
lock.Unlock() //解锁
}
func main() {
for i := 0; i < 10; i++ {
go appendValue(i)
}
for i, v := range s { //同时打印索引和值
fmt.Println(i, ":", v)
}
}
其实你只要多运行代码,你会发现还是会出现小于10条数据的情况,只是这种概率很小。
D:\workspace\go\src\test>go run main.go
0 : 2
1 : 0
2 : 4
3 : 7
4 : 1
5 : 8
6 : 3
7 : 5
8 : 9
所以个人觉得应该通过加sync.WaitGroup方法解决:
package main
import (
"fmt"
"sync"
)
var s []int
var wg sync.WaitGroup
var lock sync.Mutex //互斥锁
func appendValue(i int) {
lock.Lock() //加锁
s = append(s, i)
wg.Done()
lock.Unlock() //解锁
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go appendValue(i)
}
wg.Wait()
for i, v := range s { //同时打印索引和值
fmt.Println(i, ":", v)
}
}