简述
go的优势是高并发
原因是go + channel
一、GMP模型
大量创建go协程的代价:
内存开销:一个go 2k
调度开销:runtime.Gosched主动让出cpu
gc开销:内存回收
隐性cpu开销:协程阻塞后会创建新的M和内核线程
资源开销大的任务、任务堆积、系统任务受影响
二、协程池
1.设计
一个协程可以处理多个任务。
需求:
- 创建固定数量的协程
- 有一个任务队列,等待协程调度
- 一旦有空闲协程立即执行任务
- 协程长时间空闲就清理,一面占用空间
- 有超出时间,如果超时就让出协程
模型:
type Pool struct {
//cap 容量 pool max cap
cap int32
//running 正在运行的worker的数量
running int32
//空闲worker
workers []*Worker
//expire 过期时间 空闲的worker超过这个时间 回收掉
expire time.Duration
release 释放资源 pool就不能使用了
release chan sig
//lock 去保护pool里面的相关资源的安全
lock sync.Mutex
//once 释放只能调用一次 不能多次调用
once sync.Once }
2.初步实现
- NewPool(容量,超时释放)----注意在创造池子后需要创建协程来定时清理限制的worker
- Submit(task任务)----获取空闲的worker,将task加入worker的任务管道,将运行worker的数量+1
- GetWorker----核心业务:如果有现成的空闲的worker直接返回;如果运行的worker数量要小于池子的容量,则创建worker,并触发worker.run();否则一直轮循worker直到有worker被释放
- worker.run----另外创建协程执行worker.Task, 运行任务 ,在任务传入nil时标志执行完成。将运行worker的数量-1,将worker放回协程池。
- release----释放协程池,将worker都停止,将pool置为nil,向释放管道传入标志sig{}
3.定时清理
4.sync.pool缓存worker
在创建新的pool时,创建worker放在pool.cache中,提高性能
在getWorker时,若是没有空闲worker且容量未到达最大,先从pool.cache获取worker
在worker在超时清理或者pool释放时被停止前,先将 w放回到工作缓存中。
5.sync.Cond
Cond是一种数据类型,用于协程之间相互通信。协程间通信方式,官方推荐使用channel,channel在一对一的协程之间进行数据交换与通信十分便捷。但是,一对多的广播场景中,则显得有点无力,此时就需要sync.Cond来辅助。
}
该Cond数据类型提供的方法有:
func NewCond(l Locker) *Cond
func (c *Cond) Broadcast() // 通知所有协程,广播
func (c *Cond) Signal() // 通知一个协程
func (c *Cond) Wait() // 阻塞等待,直到被唤醒,需要持有c.lock
流程添加:
在创建pool时候创建p.Cond(&p.lock)
在阻塞等待空闲worker时调用Wait来等待,比轮询要效率高,防止cpu空转
在返回worker时调用Signal/或者在worker出错 return之前调用,使得waitworker能够再创建新的
6.任务超时释放
需要开发者在任务中加入超时处理。
7.用户自定义异常处理
pool.panicHandler—func(any)
在运行结束前recover,且如果用户有确定panic的处理方法则执行;没有定义就使用log记录错误。
协程池的效果: