Golang中切片多协程append并发不安全

2,054 阅读2分钟

并发安全也叫线程安全,在并发中出现了数据的丢失,称为并发不安全

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)
	}
}