目录
1. Goroutine
2. Memory model
3. Package sync
3.1. 多线程
- Go思想: do not communicate by sharing memory; instead, share memory by communicating
- 传统的线程模型: 在线程之间通信需要使用共享内存; 通常共享数据结构使用锁保护, 线程将争用锁来访问这些数据;
- Go 没有显示的使用锁来协调对共享数据的引用, 这种方法确保在给定的时间只有一个 goroutine 可以访问数据;
- go 推荐使用channel 来共享内存;-- chan也是用互斥锁来实现的;–包装过
- 多用于多个 goroutine 去分发任务; 而不是处理 i++ 之类的行为;
3.2. 竟态检查 detect race
竟态检查
-
data race 是指两个或多个 goroutine访问同一个资源(如 变量或数据结构), 并尝试近一些读写而不考虑其他 goroutine.
- 已产生最随机和疯狂的bug
-
处理方法
- 使用检查工具, 在测试用例中多使用, 工程项目性能稍受影响; 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
- 对于写极少,读很多的情况: 用 atomic.value 可能是性能更好的一个选择;
3.3.2. Mutex
- 互斥锁的实现原理
- Barging
- 为了提高吞吐量
- 锁释放时, 会唤醒第一个等待者, 然后把锁给与第一个等待者或是第一个请求锁的人;
- Handsoff:
- 当锁释放时, 锁会一致持有,直到第一个等待者准备好获取锁;
- 降低了吞吐量;
- 一个互斥锁的handsoff 会完美的平衡两个goroutine之间的锁分配,
- 但是会降低性能, 因为会迫使第一个goroutine等待锁;
- Spinning
- 自旋 : 在等待队列为空或者应用程序重度使用锁时, 效果不错.
- parking 和 unparking goroutines 有不低的性能成本开销, 相比自旋来说要慢很多;
- Barging
- 饥饿模式
- Go 1.8 使用 Barging 和 Spinning 结合实现;
- Go 1.9 添加了一个新的饥饿模式;
- 该模式将会在释放的时候触发Handsoff, 所有等待锁超过1ms的goroutine 被诊断为饥饿;
- handsoff 会将锁直接扔给第一个等待者;
- 饥饿模式下, 自旋会被停用;
3.4. errgroup
- 查看源码
https://pkg.go.dev/golang.org/x/sync/errgroup
核心原理: 利用 sync.Waitgroup 管理并行执行的 goroutine。
3.5. sync.pool
- 概述
- sync.pool 的场景: 用来保护和复用临时对象, 以减少内存分配,降低GC压力(Request-Driven特别合适);
- Get返回Pool中的任意一个对象, 若Pool为空, 则返回一个New的新对象;
- 放进Pool的对象, 会在说不准什么时候被回收;
- Go 1.13 版本中引入 victimcache 会将 pool 内数据拷贝一份, 避免GC将其清空;
- 即使没有使用的内容也可以保留最多两轮GC;
4. chan
4.1. channels
-
概述
- channels 是一种类型安全的消息队列, 充当两个goroutine之间的管道; 将通过他同步的进行人以资源的交换;
- chan 控制 goroutines 交互的能力从而创建了 Go同步机制;
-
应用:
- 多用于任务分发;
要了解通过 chan 交互的 goroutine 的同步行为是什么,我们需要知道通道的类型和状态。
根据我们使用的是无缓冲通道还是缓冲通道,场景会有所不同,
4.2. unbuffered channels
- 本质:
无缓冲信道的本质: 保证同步;
-
工作原理:
- 无缓冲 chan 没有容量,因此进行任何交换前需要两个 goroutine 同时准备好。
- 当 goroutine 试图将一个资源发送到一个无缓冲的通道并且没有goroutine 等待接收该资源时,该通道将锁住发送 goroutine 并使其等待。
- 当 goroutine 尝试从无缓冲通道接收,并且没有 goroutine 等待发送资源时,该通道将锁住接收 goroutine 并使其等待。
ch := make(chan struct{})
-
原则:
- receive 先于 send 发生;
- 好处: 100% 保证能够收到;
- 代价: 延迟时间未知;
4.3. buffered channels
- 本质:
有缓冲通道的本质: 异步;
-
工作原理:
- 当 goroutine 试图将资源发送到缓冲通道, 通道已满, 则锁住该 goroutine by等待缓冲区可用;
- 当 goroutine 从缓冲区接收数据, 缓冲通道为空时, 锁住该 goroutine 等待资源被发送;
-
Latencies due to under-sized buffer
- buffer的数量要适当;–过大性能未必好;
- send 先于 receive 发生;
- 不保证数据到达, 越大的的buffer, 越小的保障到达; buffer=1, 给你延迟一个消息的保障;
-
Go 并发模式 - 实例
- timing out
- moving on
- fan-out fan-in
- cancellation
- Close 先于 Receive 发生(类似 Buffered)
- 不需要传递数据,或者传递 nil。
- 非常适合取消和超时控制。
-
设计原则
- 使用最小的 buffer 获取足够的吞吐量;
5. Package context
-
概述
- Go 1.7 引入一个 context 包,它使得跨 API 边界的请求范围元数据、取消信号和截止日期很容易传递给处理请求所涉及的所有 goroutine(显示传递)
- context 是面向请求的;
-
原则
- request-scoped context
-
将context集成到API中