数据竞争和内存重分配Golang slice并发不安全问题解决

 更新时间:2023年10月13日 09:20:31   作者:及尔偕老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并发安全的资料请关注脚本之家其它相关文章!

相关文章

  • Go内置零值标识符zero

    Go内置零值标识符zero

    大家学习和使用 Go 语言时,有一个神奇的概念:零值(zero-values),所以本文想给大家分享一个关于零值的新提案,目测已经八九不离十了
    2023-08-08
  • Golang中实现类似类与继承的方法(示例代码)

    Golang中实现类似类与继承的方法(示例代码)

    这篇文章主要介绍了Golang中实现类似类与继承的方法,Go语言中通过方法接受者的类型来决定方法的归属和继承关系,本文通过示例代码讲解的非常详细,需要的朋友可以参考下
    2024-04-04
  • 获取Golang环境变量的三种方式小结

    获取Golang环境变量的三种方式小结

    本文介绍了Golang中获取环境变量的三种方式,包含使用Viper包、GoDotEnv包和os包,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-11-11
  • golang并发编程的实现

    golang并发编程的实现

    这篇文章主要介绍了golang并发编程的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • 如何将Golang数组slice转为逗号分隔的string字符串

    如何将Golang数组slice转为逗号分隔的string字符串

    这篇文章主要介绍了如何将Golang数组slice转为逗号分隔的string字符串问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • 如何利用Golang写出高并发代码详解

    如何利用Golang写出高并发代码详解

    今天领导问起为什么用Golang,同事回答语法简单,语言新,支持高并发。那高并发到底如何实现,下面这篇文章主要给大家介绍了关于如何利用Golang写出高并发代码的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09
  • GoLang分布式锁与snowflake雪花算法

    GoLang分布式锁与snowflake雪花算法

    这篇文章主要介绍了GoLang分布式锁与snowflake雪花算法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2022-12-12
  • Go语言的WebAssembly实战开发

    Go语言的WebAssembly实战开发

    Go语言的WebAssembly支持为Web开发带来了新的可能性,本文主要介绍了Go语言与WebAssembly的结合,内容包括编译方法、JavaScript交互、性能优化技巧和实战案例,感兴趣的可以了解一下
    2026-05-05
  • Golang学习之无类型常量详解

    Golang学习之无类型常量详解

    对于无类型常量,可能大家是第一次听说,但我们每天都在用,每天都有无数潜在的坑被埋下。本文就来和大家聊聊它的相关注意事项吧,希望对大家有所帮助
    2023-03-03
  • Golang中crypto/ecdsa库实现数字签名和验证

    Golang中crypto/ecdsa库实现数字签名和验证

    本文主要介绍了Golang中crypto/ecdsa库实现数字签名和验证,将从ECDSA的基本原理出发,详细解析如何在Go语言中实现数字签名和验证,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02

最新评论