数据竞争和内存重分配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语言中的数组和切片

    深入理解Go语言中的数组和切片

    Go语言中的数组大概相当与C/C++中的数组,固定大小,不能够动态扩展大小,而切片大概相当与C++中的Vector,可以动态扩展大小,当大小超过容量时,重新分配一块内存,然后将数据复制到新的内存区域。下面我们通过几个问题来更好理解golang 的数组和切片,一起来看看吧。
    2016-09-09
  • MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法

    MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法

    这篇文章主要介绍了MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • 详解Go中如何进行进行内存优化和垃圾收集器管理

    详解Go中如何进行进行内存优化和垃圾收集器管理

    这篇文章主要为大家详细介绍了Go中如何进行进行内存优化和垃圾收集器管理,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解下
    2023-11-11
  • Golang中文件目录操作的实现步骤详解

    Golang中文件目录操作的实现步骤详解

    在Golang中,文件目录是指计算机文件系统中的文件夹或目录。目录是用于组织和存储文件的一种方式,可以包含文件和其他子目录,本文主要介绍了Golang中文件目录操作的实现方法,需要的朋友可以参考下
    2023-05-05
  • golang之数据验证validator的实现

    golang之数据验证validator的实现

    这篇文章主要介绍了golang之数据验证validator的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Golang的strings.Split()踩坑记录

    Golang的strings.Split()踩坑记录

    工作中,当我们需要对字符串按照某个字符串切分成字符串数组数时,常用到strings.Split(),本文主要介绍了Golang的strings.Split()踩坑记录,感兴趣的可以了解一下
    2022-05-05
  • go语言中嵌套结构体的实现

    go语言中嵌套结构体的实现

    在Go语言中,嵌套结构体可定义为一个结构体内包含另一个结构体,嵌套可以是值嵌套或指针嵌套,两者在内存分配和修改影响上有显著区别,本文就来详细的介绍一下,感兴趣的可以了解一下
    2024-09-09
  • golang 实现tcp server端和client端,并计算RTT时间操作

    golang 实现tcp server端和client端,并计算RTT时间操作

    这篇文章主要介绍了golang 实现tcp server端和client端,并计算RTT时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang算法问题之整数拆分实现方法分析

    Golang算法问题之整数拆分实现方法分析

    这篇文章主要介绍了Golang算法问题之整数拆分实现方法,结合实例形式分析了Go语言数值运算与数组遍历相关操作技巧,需要的朋友可以参考下
    2017-02-02
  • Go语言常见错误之将接口定义在实现方

    Go语言常见错误之将接口定义在实现方

    在Go中,接口起到一个十分关键的角色,它们提供了一种方式来定义对象的行为,而不需要知道对象的具体实现,一个常见的错误是在实现方而不是使用方定义接口,本文将详细探讨为何这样做是一个错误,以及如何避免它
    2024-01-01

最新评论