• Go并发编程之内置数据结构


     

    sync.Pool

    保存和复用临时对象,减少内存分配,降低 GC 压力。[2]

     声明对象池,只需要实现 New 函数即可。对象池中没有对象时,将会调用 New 函数创建。

    1. var studentPool = sync.Pool{
    2. New: func() interface{} {
    3. return new(Student)
    4. },
    5. }
    1. stu := studentPool.Get().(*Student)
    2. json.Unmarshal(buf, stu)
    3. studentPool.Put(stu)
    • Get() 用于从对象池中获取对象,因为返回值是 interface{},因此需要类型转换。
    • Put() 则是在对象使用完毕后,返回对象池。

     

    sync.Once

    Go 标准库提供的使函数只执行一次的实现,常应用于单例模式,例如初始化配置、保持数据库连接等[2]

    sync.Cond

    sync.Cond 条件变量用来协调想要访问共享资源的那些 goroutine,当共享资源的状态发生变化的时候,它可以用来通知被互斥锁阻塞的 goroutine。[2]

    sync.Mutex&sync.RWMutex

    Go语言中sync库提供了两种锁互斥锁(sync.Mutex)和读写锁(sync.RWMutex)

    互斥锁,互斥即不可同时运行。即使用了互斥锁的两个代码片段互相排斥,只有其中一个代码片段执行完成后,另一个才能执行。

    Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:

    • Lock 加锁
    • Unlock 释放锁

    读写锁,分为读锁和写锁,读锁是允许同时执行的,但写锁是互斥的。一般来说,有如下几种情况:

    • 读锁之间不互斥,没有写锁的情况下,读锁是无阻塞的,多个协程可以同时获得读锁。
    • 写锁之间是互斥的,存在写锁,其他写锁阻塞。
    • 写锁与读锁是互斥的,如果存在读锁,写锁阻塞,如果存在写锁,读锁阻塞。

    Go 标准库中提供了 sync.RWMutex 读写锁类型及其四个方法:

    • Lock 加写锁
    • Unlock 释放写锁
    • RLock 加读锁
    • RUnlock 释放读锁

    读写锁的存在是为了解决读多写少时的性能问题,读场景较多时,读写锁可有效地减少锁阻塞的时间

    假设每个读写操作为1微妙
    读写比为 9:1 时,读写锁的性能约为互斥锁的 8 倍
    读写比为 1:9 时,读写锁性能相当
    读写比为 5:5 时,读写锁的性能约为互斥锁的 2 倍

    [2]

    sync.Map

    相比于数据类型map,sync.Map是一个并发安全的map

    Go标准库中的Map提供了六个方法

    Delete(key any) 删除key

    Load(key any) (value any, ok bool) 读取key

    LoadAndDelete(key any) (value any, loaded bool) 读取并删除

    LoadOrStore(key, value any) (actual any, loaded bool) 读取,不存在则存储

    Range(f func(key, value any) bool) 遍历MAP

    Store(key, value any) 存储key,value

    sync.Waitgroup

    WaitGroup就是package sync用来做任务编排的一个并发原语。这个要解决的就是并发-等待的问题

    Go标准库中的WaitGroup提供了三个方法 

    • Add,用来设置WaitGroup的计数值;
    • Dome,用来将WaitGroup的计数值减1,其实就是调用了Add(-1);
    • Wait,调用这个方法的goroutine会一直阻塞,直到WaitGroup的计数值变为0。

    使用WaitGroup时的常见错误

            常见问题一:计数器设置为负值

    •  调用Add的时候传递一个负数,计算器值加起来小于0
    1. func main() {
    2. var wg sync.WaitGroup
    3. wg.Add(10)
    4. wg.Add(-10)// 将-10作为参数调用Add,计数值被设置为0
    5. wg.Add(-1)//panic
    6. }
    • 调用Done方法的次数过多,超过了WaitGroup的计数值
    1. func main() {
    2. var wg sync.WaitGroup
    3. wg.Add(1)
    4. wg.Done()
    5. wg.Done()
    6. }

            常见问题二:Add时机的时机不对

            使用WaitGroup一定要遵守的原则就是,等所有的Add方法调用之后再调用Wait否则可能导致panic或者不期望的结果

            常见问题三:前一个Wait还没有结束就重用WaitGroup

            WaitGroup 是可以重用的。只要 WaitGroup 的计数值恢复到零值的状态

    1. func main() {
    2. var wg sync.WaitGroup
    3. wg.Add(1)
    4. go func() {
    5. time.Sleep(time.Millisecond)
    6. wg.Done() // 计数器减1
    7. wg.Add(1) // 计数值加1
    8. }()
    9. wg.Wait() // 主goroutine等待,有可能和第7行并发执行
    10. }

    References

    1.【并发编程】WaitGroup 基本用法和如何实现以及常见错误

    2.Go语言高性能编程之并发编程

  • 相关阅读:
    ZPL II 语言编程基础
    数据库SqlServer笔试题
    Oracle 21版Database In-Memory LivaLabs实验(下)
    LeetCode每日一练 —— 剑指Offer 56 数组中数字出现的次数
    ESP8266 如何使用 GPIO13 & GPIO15 进行 UART0 通信?
    将一个硬盘空间分配给另一个硬盘,怎么把一个磁盘的空间给另一个磁盘
    图像分块及拼接(二)python代码
    计算机组成原理知识——CPU结构组成和功能、堆栈、RISC、
    Redis持久化、主从与哨兵架构详解
    HAL_UART_Receive不能正常超时返回
  • 原文地址:https://blog.csdn.net/qq_37186127/article/details/125528790