数据竞争和内存重分配Golang slice并发不安全问题解决
作者:及尔偕老lp
这篇文章主要为大家介绍了数据竞争和内存重分配Golang slice并发不安全问题解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
Golang 中的 slice 为什么是并发不安全的?
一、并发不安全的
在Go语言中,slice是并发不安全的,主要有以下两个原因:数据竞争、内存重分配。
数据竞争:slice底层的结构体包含一个指向底层数组的指针和该数组的长度,当多个协程并发访问同一个slice时,有可能会出现数据竞争的问题。例如,一个协程在修改slice的长度,而另一个协程同时在读取或修改slice的内容。
内存重分配:在向slice中追加元素时,可能会触发slice的扩容操作,在这个过程中,如果有其他协程访问了slice,就会导致指向底层数组的指针出现异常。
二、并发场景
多个协程同时向 slice 追加元素,会有一部分元素被追加到了旧的底层数组里,最终 slice 的长度小于目标值。
func main() { a := make([]int, 0) for i := 0; i < 10000; i++ { go func(i int) { a = append(a, i) }(i) } fmt.Println(len(a)) // 9015 < 10000 }
三、实现 slice 并发安全
要实现 slice 并发安全,有两种方法:加互斥锁、使用channel串行化操作。
方式一:使用互斥锁 sync.Mutex
追加元素之前调用 Lock() 函数加锁,追加完后,调用 Unlock() 解锁。
func main() { var lock sync.Mutex //互斥锁 a := make([]int, 0) var wg sync.WaitGroup for i := 0; i < 10000; i++ { wg.Add(1) go func(i int) { defer wg.Done() lock.Lock() defer lock.Unlock() a = append(a, i) }(i) } wg.Wait() fmt.Println(len(a)) // equal 10000 }
最终 slice 的长度等于目标值。
方式二:使用channel串行化操作
生产者生产元素,发送到通道中,消费者从通道中接收元素,追加到 slice 中。使用无缓冲通道,接收方、发送方必须同时存在,负责任意一方都会阻塞。
func main() { buffer := make(chan int) a := make([]int, 0) // 消费者 go func() { for v := range buffer { a = append(a, v) } }() // 生产者 var wg sync.WaitGroup for i := 0; i < 10000; i++ { wg.Add(1) go func(i int) { defer wg.Done() buffer <- i }(i) } wg.Wait() fmt.Println(len(a)) // equal 10000 }
最终 slice 的长度等于目标值。
两种方式的比较
加互斥锁适合于对性能要求不高的场景,毕竟锁的粒度太大,这种方式属于通过共享内存来实现通信。channle 适合于对性能要求大的场景,channle 就是专用于 goroutine 间通信的,这种方式属于通过通信来实现共享内存。
以上就是数据竞争和内存重分配Golang slice并发不安全问题解决的详细内容,更多关于Golang slice并发安全的资料请关注脚本之家其它相关文章!