【Go进阶】03 并行编程

目录


1. Goroutine


2. Memory model


3. Package sync

3.1. 多线程

  1. Go思想: do not communicate by sharing memory; instead, share memory by communicating
    1. 传统的线程模型: 在线程之间通信需要使用共享内存; 通常共享数据结构使用锁保护, 线程将争用锁来访问这些数据;
    2. Go 没有显示的使用锁来协调对共享数据的引用, 这种方法确保在给定的时间只有一个 goroutine 可以访问数据;
  2. go 推荐使用channel 来共享内存;-- chan也是用互斥锁来实现的;–包装过
  3. 多用于多个 goroutine 去分发任务; 而不是处理 i++ 之类的行为;

3.2. 竟态检查 detect race

竟态检查

  1. data race 是指两个或多个 goroutine访问同一个资源(如 变量或数据结构), 并尝试近一些读写而不考虑其他 goroutine.

    1. 已产生最随机和疯狂的bug
  2. 处理方法

    - 使用检查工具, 在测试用例中多使用, 工程项目性能稍受影响;
    go build -race
    go test -race
    
    - 示例: 
    import (
       "fmt"
       "sync"
       "time"
    )
    
    var Wait sync.WaitGroup
    var Couter int = 0
    
    func main() {
       for routine := 1; routine <= 2; routine++ {
          Wait.Add(1)
          go Routine(routine)
       }
    
       Wait.Wait()
       fmt.Printf("Final Counter: %d\n", Couter)
    }
    
    func Routine(id int) {
       for count := 0; count < 2; count++ {
          value := Couter
          time.Sleep(1 * time.Nanosecond)
          value++
          Couter = value
       }
    
       Wait.Done()
    }
    
    
    -----------------
    
    

3.3. 同步问题

3.3.1. sync.atomic

  1. 对于写极少,读很多的情况: 用 atomic.value 可能是性能更好的一个选择;

3.3.2. Mutex

  • 互斥锁的实现原理
    1. Barging
      1. 为了提高吞吐量
      2. 锁释放时, 会唤醒第一个等待者, 然后把锁给与第一个等待者或是第一个请求锁的人;
    2. Handsoff:
      1. 当锁释放时, 锁会一致持有,直到第一个等待者准备好获取锁;
      2. 降低了吞吐量;
      • 一个互斥锁的handsoff 会完美的平衡两个goroutine之间的锁分配,
      • 但是会降低性能, 因为会迫使第一个goroutine等待锁;
    3. Spinning
      1. 自旋 : 在等待队列为空或者应用程序重度使用锁时, 效果不错.
      2. parking 和 unparking goroutines 有不低的性能成本开销, 相比自旋来说要慢很多;
  1. 饥饿模式
    1. Go 1.8 使用 Barging 和 Spinning 结合实现;
    2. Go 1.9 添加了一个新的饥饿模式;
      1. 该模式将会在释放的时候触发Handsoff, 所有等待锁超过1ms的goroutine 被诊断为饥饿;
      2. handsoff 会将锁直接扔给第一个等待者;
      3. 饥饿模式下, 自旋会被停用;

3.4. errgroup

  • 查看源码
    https://pkg.go.dev/golang.org/x/sync/errgroup
    核心原理: 利用 sync.Waitgroup 管理并行执行的 goroutine。

3.5. sync.pool

  1. 概述
    1. sync.pool 的场景: 用来保护和复用临时对象, 以减少内存分配,降低GC压力(Request-Driven特别合适);
    2. Get返回Pool中的任意一个对象, 若Pool为空, 则返回一个New的新对象;
    3. 放进Pool的对象, 会在说不准什么时候被回收;
    4. Go 1.13 版本中引入 victimcache 会将 pool 内数据拷贝一份, 避免GC将其清空;
      1. 即使没有使用的内容也可以保留最多两轮GC;

4. chan

4.1. channels

  1. 概述

    1. channels 是一种类型安全的消息队列, 充当两个goroutine之间的管道; 将通过他同步的进行人以资源的交换;
    2. chan 控制 goroutines 交互的能力从而创建了 Go同步机制;
  2. 应用:

    1. 多用于任务分发;
要了解通过 chan 交互的 goroutine 的同步行为是什么,我们需要知道通道的类型和状态。
根据我们使用的是无缓冲通道还是缓冲通道,场景会有所不同,

4.2. unbuffered channels

  • 本质:
    无缓冲信道的本质: 保证同步;
  1. 工作原理:

    1. 无缓冲 chan 没有容量,因此进行任何交换前需要两个 goroutine 同时准备好。
    2. 当 goroutine 试图将一个资源发送到一个无缓冲的通道并且没有goroutine 等待接收该资源时,该通道将锁住发送 goroutine 并使其等待。
    3. 当 goroutine 尝试从无缓冲通道接收,并且没有 goroutine 等待发送资源时,该通道将锁住接收 goroutine 并使其等待。
    ch := make(chan struct{})
    
  2. 原则:

    1. receive 先于 send 发生;
    2. 好处: 100% 保证能够收到;
    3. 代价: 延迟时间未知;

4.3. buffered channels

  • 本质:
    有缓冲通道的本质: 异步;
  1. 工作原理:

    1. 当 goroutine 试图将资源发送到缓冲通道, 通道已满, 则锁住该 goroutine by等待缓冲区可用;
    2. 当 goroutine 从缓冲区接收数据, 缓冲通道为空时, 锁住该 goroutine 等待资源被发送;
  2. Latencies due to under-sized buffer

    1. buffer的数量要适当;–过大性能未必好;
    2. send 先于 receive 发生;
    3. 不保证数据到达, 越大的的buffer, 越小的保障到达; buffer=1, 给你延迟一个消息的保障;
  3. Go 并发模式 - 实例

    1. timing out
    2. moving on
    3. fan-out fan-in
    4. cancellation
    • Close 先于 Receive 发生(类似 Buffered)
    • 不需要传递数据,或者传递 nil。
    • 非常适合取消和超时控制。
  4. 设计原则

    1. 使用最小的 buffer 获取足够的吞吐量;

5. Package context

  1. 概述

    1. Go 1.7 引入一个 context 包,它使得跨 API 边界的请求范围元数据、取消信号和截止日期很容易传递给处理请求所涉及的所有 goroutine(显示传递)
    2. context 是面向请求的;
  2. 原则

    1. request-scoped context
  3. 将context集成到API中


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值