欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > Golang笔记——数组、Slice、Map、Channel的并发安全性

Golang笔记——数组、Slice、Map、Channel的并发安全性

2025/2/25 21:01:32 来源:https://blog.csdn.net/haopingbiji/article/details/145109491  浏览:    关键词:Golang笔记——数组、Slice、Map、Channel的并发安全性

大家好,这里是Good Note,关注 公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Golang常用数据类型的并发安全性,特别是复合数据类型(数组、Slice、Map、Channel)的并发安全性。

在这里插入图片描述

文章目录

    • 线程安全(Thread Safety)定义
    • 1. 数组
      • 非并发安全性
      • 高并发下的使用注意
      • 示例
        • 并发写入数组(需加锁)
    • 2. 切片
      • 非并发安全性
      • 高并发下的使用注意
      • 示例
        • 并发写入切片(需加锁)
        • 错误示例:并发读写切片
    • 3. Map
      • 非并发安全性
      • 高并发下的使用注意
      • 示例
        • 错误示例
        • 正确用法 1:使用 `sync.Map`
        • 正确用法 2:使用 `sync.Mutex`
    • 4. Channel
      • 并发安全性
      • 高并发下的使用注意
      • 示例
        • 正确用法
        • 错误示例:多次关闭 `channel`
    • 总结
    • 历史文章
      • MySQL数据库
      • Redis
      • Golang

线程安全(Thread Safety)定义

线程安全(Thread Safety) 是指在多线程(或多 Goroutine)环境下,多个线程或 Goroutine 同时访问共享数据时,不会导致数据不一致或程序出现异常行为的特性


1. 数组

非并发安全性

  • 不线程安全:数组是 Go 的值类型,对同一个数组的并发访问(读或写)会导致数据竞争。
  • 特点
    1. 多个 Goroutine 对同一个数组同时进行写操作可能会破坏数据一致性
    2. 如果仅进行并发读操作,数组是线程安全的

高并发下的使用注意

  1. 对数组的读写操作需要通过锁(如 sync.Mutexsync.RWMutex)或其他同步机制来保护。
  2. 如果对数组进行复制后再操作,副本是独立的,可以避免并发问题。
  3. 在高并发场景中,直接使用数组的性能和扩展性较差,建议使用切片或其他并发友好的数据结构。
  1. 数组的固定大小和值类型特性导致性能和扩展性较差,需要显式的锁机制来确保线程安全。
  2. 切片通过动态扩容和引用传递解决了数组的局限性,并提供更高的灵活性和更低的内存开销,因此更适合高并发场景。

示例

并发写入数组(需加锁)
package mainimport ("fmt""sync"
)func main() {var arr = [5]int{}var mu sync.Mutexvar wg sync.WaitGroupfor i := 0; i < len(arr); i++ {wg.Add(1)go func(index int) {defer wg.Done()mu.Lock()arr[index] = index * 10mu.Unlock()}(i)}wg.Wait()fmt.Println(arr)
}

2. 切片

非并发安全性

  • 不线程安全:切片是对底层数组的引用。高并发下对同一个切片的操作(如扩容、写入等)会导致数据竞争或崩溃。
  • 问题来源
    1. 切片的长度和容量在运行时动态变化,扩容操作会重新分配底层数组,并可能导致未同步的 Goroutine 操作过期引用。
    2. 并发写入同时进行读写会破坏底层数据的一致性。

高并发下的使用注意

  1. 读写分离
    • 如果多个 Goroutine 并发读切片,而没有写入操作,则是线程安全的。
    • 一旦存在写操作,需要使用同步机制(如 sync.Mutexsync.RWMutex)保护。
  2. 扩容风险
    • 切片扩容时会重新分配底层数组,建议在并发环境中提前设置好切片的容量,避免动态扩容带来的问题

示例

并发写入切片(需加锁)
package mainimport ("fmt""sync"
)func main() {slice := []int{}var mu sync.Mutexvar wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(val int) {defer wg.Done()mu.Lock()slice = append(slice, val)mu.Unlock()}(i)}wg.Wait()fmt.Println(slice)
}

输出:

  • 解释:并发写入切片的操作使用了 sync.Mutex,这确保了同一时刻只有一个 Goroutine 能访问切片并进行 append 操作,因此数据不会发生竞争,程序不会崩溃。
  • 输出的顺序slice 最终会包含从 0 到 9 的所有数字,但由于并发执行的特性,数字的顺序通常是无序的。这是因为虽然对切片的写操作是互斥的,但 Goroutine 执行的顺序依然是不可预测的,所以 append 操作的顺序也无法保证。

如果希望按照顺序(0-9)输出,可以通过使用 channel 来保证按顺序收集并输出结果,或使用其他同步方式如 sync.Once 来协调。这属于Go协程同步的问题。

错误示例:并发读写切片
package mainimport ("fmt""sync"
)func main() {var wg sync.WaitGroupslice := []int{}for i := 0; i < 10; i++ {wg.Add(1) // 为每个 Goroutine 添加一个计数go func(val int) {defer wg.Done()            // 每个 Goroutine 执行完成后,计数器减一slice = append(slice, val) // 并发写入切片}(i)}wg.Wait()          // 等待所有 Goroutine 执行完毕fmt.Println(slice) // 输出结果
}

输出:

  • 输出个数不确定:因为并发写入时发生了数据竞争切片在扩容时可能丢失元素。
  • append 会在切片的底层数组容量不足时进行扩容,这个扩容过程是 不安全的,并且切片的底层数组是指向连续内存空间的。如果多个 Goroutine 同时执行 append 操作并且触发了扩容,就可能导致数据丢失或不可预期的行为。

3. Map

非并发安全性

  • 不线程安全:Go 的原生 map 类型在并发访问时不安全,直接进行并发读写会导致数据竞争或运行时崩溃(fatal error: concurrent map read and map write)。
  • 原因
    1. map 的底层实现没有锁机制,多个 Goroutine 同时修改 map 的内部结构会破坏其一致性。
    2. 读写操作之间没有同步机制,可能导致部分数据状态不一致。

高并发下的使用注意

  1. 使用 sync.Map
    • sync.Map 是线程安全的 map,支持高并发场景。
    • 缺点:性能可能低于加锁的原生 map,尤其在频繁读写情况下。
  2. 使用 锁机制
    • 使用 sync.Mutexsync.RWMutex 对原生 map 加锁。

示例

错误示例
package mainimport ("fmt""sync"
)func main() {m := make(map[int]int)var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(val int) {defer wg.Done()m[val] = val}(i)}wg.Wait()fmt.Println(m)
}

输出:fatal error: concurrent map writes

正确用法 1:使用 sync.Map
package mainimport ("fmt""sync"
)func main() {var m sync.Mapvar wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(val int) {defer wg.Done()m.Store(val, val) // 并发安全的写入}(i)}wg.Wait()// 遍历 sync.Mapm.Range(func(key, value any) bool {fmt.Printf("key: %v, value: %v\n", key, value)return true})
}

输出示例:

key: 0, value: 0
key: 9, value: 9
key: 7, value: 7
key: 3, value: 3
key: 5, value: 5
key: 2, value: 2
key: 6, value: 6
key: 8, value: 8
key: 4, value: 4
key: 1, value: 1
正确用法 2:使用 sync.Mutex
package mainimport ("fmt""sync"
)func main() {m := make(map[int]int)var mu sync.Mutexvar wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func(val int) {defer wg.Done()mu.Lock()m[val] = valmu.Unlock()}(i)}wg.Wait()fmt.Println(m)
}

输出示例:

map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9]

4. Channel

并发安全性

channel的源码中使用了互斥锁,不允许并发读写。详细参考:Go语言面试之——channel

  • 线程安全:Go 的 channel 是线程安全的,支持在多个 Goroutine 中并发进行读写操作。
  • 注意事项
    1. 关闭 channel
      • 关闭一个 channel 必须由单个 Goroutine 执行,多个 Goroutine 并发关闭会导致运行时错误(panic: close of closed channel)。
    2. 读写阻塞
      • 如果没有消费者读取数据,发送到 channel 的数据会阻塞发送 Goroutine。
      • 如果没有生产者发送数据,读取 channel 会阻塞接收 Goroutine。

高并发下的使用注意

  1. 保证关闭操作由单一 Goroutine 处理
  2. 根据需求设置缓冲区大小
    • 对于高并发场景,可以使用带缓冲的 channel 来提高性能。
  3. 避免死锁
    • 设计时确保 channel 的生产者和消费者平衡。

示例

正确用法
package mainimport ("fmt""sync"
)func main() {ch := make(chan int, 5)var wg sync.WaitGroup// 并发写入for i := 0; i < 5; i++ {wg.Add(1)go func(val int) {defer wg.Done()ch <- val}(i)}// 关闭通道go func() {wg.Wait()close(ch) // 单一 Goroutine 负责关闭}()// 读取数据for val := range ch {fmt.Println(val)}
}
错误示例:多次关闭 channel
package mainfunc main() {ch := make(chan int)go func() {close(ch) // 第一次关闭}()go func() {close(ch) // 第二次关闭,导致 panic}()select {}
}

输出异常:panic: close of closed channel


总结

数据结构是否线程安全高并发注意事项
数组需要加锁保护访问;在并发场景中,数组性能较差,建议使用切片或其他更灵活的结构。
切片切片是引用类型,需要加锁保护并发访问,尤其是扩容场景;多读无写时是线程安全的。
Map使用 sync.Map 或加锁实现并发安全;直接并发读写会导致数据竞争或崩溃。
Channel本身线程安全;但关闭操作需由单一 Goroutine 执行,避免多次关闭或不平衡的生产者/消费者造成死锁。

通过合理的设计和同步机制,可以在高并发场景中安全地使用这些数据结构。

历史文章

MySQL数据库

MySQL数据库

Redis

Redis数据库笔记合集

Golang

  1. Golang笔记——语言基础知识
  2. Golang笔记——切片与数组
  3. Golang笔记——hashmap
  4. Golang笔记——rune和byte
  5. Golang笔记——channel
  6. Golang笔记——Interface类型

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词