大家好,我是dandyhuang,上次給大家解釋了連接池,對(duì)象池。本次給大家介紹一下ants v2.4.6
協(xié)程池的實(shí)現(xiàn)
協(xié)程池
提到協(xié)程,大家可能會(huì)覺(jué)得協(xié)程已經(jīng)夠輕量了,為什么還需要在引入?yún)f(xié)程池呢。有些小伙伴可能會(huì)覺(jué)得多此一舉。其實(shí)不然,每次創(chuàng)建一個(gè)goroutine大小大概在2k左右,如果服務(wù)器并非很高,goroutine占用幾十萬(wàn)個(gè)。那其實(shí)協(xié)程的資源占用也會(huì)相當(dāng)高的。有些可能還會(huì)因?yàn)闆](méi)有處理邏輯帶來(lái)goroutine的泄漏。這些情況都會(huì)不僅沒(méi)有給服務(wù)器帶來(lái)性能提升,返而帶來(lái)服務(wù)可用性下降。所以,不同場(chǎng)景,我們還是需要使用一些協(xié)程池來(lái)進(jìn)行管控的
ants使用
func main() {
pool, _ := ants.NewPool(2)
runTimes := 10
var wg sync.WaitGroup
syncCalculateSum := func() {
func() {
time.Sleep(10 * time.Millisecond)
fmt.Println("Hello World!")
}()
wg.Done()
}
for i := 0; i < runTimes; i++ {
wg.Add(1)
_ = pool.Submit(syncCalculateSum)
}
wg.Wait()
pool.Release()
}
Pool結(jié)構(gòu)
// 不要嵌套使用Pool
type Pool struct {
// 容量設(shè)置
capacity int32
// 當(dāng)前正在執(zhí)行的g有多少個(gè)
running int32
// locker queue
lock sync.Locker
// 存儲(chǔ)workers
workers workerArray
// 池的狀態(tài)
state int32
// 條件變量
cond *sync.Cond
// worker緩存
workerCache sync.Pool
// 阻塞的數(shù)量
blockingNum int
// 參數(shù)設(shè)置
options *Options
}
Pool結(jié)構(gòu)還是很清晰簡(jiǎn)單,這里提醒了我們,pool不要嵌套使用
NewPool初始化協(xié)程池
func NewPool(size int, options ...Option) (*Pool, error) {
// 常用的Functional Options模式設(shè)置一些基本配置
opts := loadOptions(options...)
// 邊界判斷
if size <= 0 {
size = -1
}
// 過(guò)期時(shí)間檢測(cè)
if expiry := opts.ExpiryDuration; expiry < 0 {
return nil, ErrInvalidPoolExpiry
} else if expiry == 0 {
opts.ExpiryDuration = DefaultCleanIntervalTime
}
// 沒(méi)有設(shè)置logger就是用默認(rèn)的
if opts.Logger == nil {
opts.Logger = defaultLogger
}
// 設(shè)置容量,并且自己實(shí)現(xiàn)了一個(gè)自旋鎖
p := &Pool{
capacity: int32(size),
lock: internal.NewSpinLock(),
options: opts,
}
// sync.pool緩存池
p.workerCache.New = func() interface{} {
return &goWorker{
pool: p,
task: make(chan func(), workerChanCap),
}
}
// 棧存儲(chǔ)或者隊(duì)列存儲(chǔ)
if p.options.PreAlloc {
if size == -1 {
return nil, ErrInvalidPreAllocSize
}
p.workers = newWorkerArray(loopQueueType, size)
} else {
p.workers = newWorkerArray(stackType, 0)
}
// 初始化條件變量
p.cond = sync.NewCond(p.lock)
// 定時(shí)清理過(guò)期的workers(后面分析)
go p.purgePeriodically()
return p, nil
}
Submit
ants.jpeg
func (p *Pool) Submit(task func()) error {
// 判斷池是否關(guān)閉
if p.IsClosed() {
return ErrPoolClosed
}
var w *goWorker
// 獲取worker
if w = p.retrieveWorker(); w == nil {
return ErrPoolOverload
}
// 將task存到channel中
w.task <- task
return nil
}
// 獲取worker
func (p *Pool) retrieveWorker() (w *goWorker) {
spawnWorker := func() {
// 第一次獲取不到,創(chuàng)建一個(gè)
w = p.workerCache.Get().(*goWorker)
// goworker執(zhí)行任務(wù)
w.run()
}
// 上鎖不成功讓出G調(diào)度的時(shí)間片
p.lock.Lock()
// 從棧或者隊(duì)列中獲取goWorker
w = p.workers.detach()
if w != nil { // first try to fetch the worker from the queue
p.lock.Unlock()
} else if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
// 隊(duì)列沒(méi)有獲取到,并且還沒(méi)有超過(guò)容量,在獲取一個(gè)
p.lock.Unlock()
spawnWorker()
} else { // otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
// 非阻塞直接返回
if p.options.Nonblocking {
p.lock.Unlock()
return
}
retry:
// 最大阻塞的任務(wù)數(shù)
if p.options.MaxBlockingTasks != 0 && p.blockingNum >= p.options.MaxBlockingTasks {
p.lock.Unlock()
return
}
p.blockingNum++
// 等待可用的worker,并釋放鎖,和c艸一樣的
p.cond.Wait() // block and wait for an available worker
p.blockingNum--
var nw int
// 喚醒后,如果池中已經(jīng)被清除,就在創(chuàng)建一個(gè)goroutine
if nw = p.Running(); nw == 0 { // awakened by the scavenger
p.lock.Unlock()
if !p.IsClosed() {
spawnWorker()
}
return
}
// 獲取到goWoker并且為空,說(shuō)明被情況,就在創(chuàng)建一個(gè)goroutine
if w = p.workers.detach(); w == nil {
if nw < capacity {
p.lock.Unlock()
spawnWorker()
return
}
goto retry
}
// 解鎖
p.lock.Unlock()
}
return
}
- 從隊(duì)列中獲取goWorker,開(kāi)始沒(méi)有,所以調(diào)用spawnWorker,調(diào)用goworker.run(),啟動(dòng)goroutine執(zhí)行方法。這時(shí)候task為空,阻塞等待隊(duì)列
- 如果detach獲取到隊(duì)列,則直接返回,并把task放到w.task中,task獲取任務(wù)執(zhí)行回調(diào)
- 如果超過(guò)容量,如果是阻塞判斷最大阻塞數(shù)量,并wait等待其他隊(duì)列處理完畢,如果池子沒(méi)有被過(guò)期清楚,那么繼續(xù)時(shí)候。
- 這里將w放到sync.pool里頭,還是挺好的ideal
goWorker執(zhí)行任務(wù)
func (w *goWorker) run() {
// 增加running的個(gè)數(shù)
w.pool.incRunning()
go func() {
defer func() {
// 減少running數(shù)
w.pool.decRunning()
// 講w放到sync.pool中
w.pool.workerCache.Put(w)
// 如果處理的任務(wù)panic了,捕獲一下
if p := recover(); p != nil {
if ph := w.pool.options.PanicHandler; ph != nil {
// 如果有設(shè)置panihandle就調(diào)用
ph(p)
} else {
w.pool.options.Logger.Printf("worker exits from a panic: %v\n", p)
var buf [4096]byte
n := runtime.Stack(buf[:], false)
// 沒(méi)有輸出堆棧信息
w.pool.options.Logger.Printf("worker exits from panic: %s\n", string(buf[:n]))
}
}
// 條件變量通知其他等待的g
w.pool.cond.Signal()
}()
// 阻塞等待task
for f := range w.task {
// task被過(guò)期清空或者釋放
if f == nil {
return
}
f()
// 將w存到pool.workers中下次可以再次獲取
if ok := w.pool.revertWorker(w); !ok {
return
}
}
}()
}
- 等待獲取task,并將w存放到pool.wokers中
- 退出后,講w放到pool.wokercache中,如果task panic,那么recover住。之后signal其他隊(duì)列
存儲(chǔ)worker
func (p *Pool) revertWorker(worker *goWorker) bool {
//判斷running和close等問(wèn)題
if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
return false
}
// 設(shè)置清理時(shí)間
worker.recycleTime = time.Now()
p.lock.Lock()
// release狀態(tài)變更,所以需要在判斷一次
if p.IsClosed() {
p.lock.Unlock()
return false
}
// 存入workers中
err := p.workers.insert(worker)
if err != nil {
p.lock.Unlock()
return false
}
// 喚醒通知等待的worker
p.cond.Signal()
p.lock.Unlock()
return true
}
定期清除
func (p *Pool) purgePeriodically() {
// 定時(shí)器
heartbeat := time.NewTicker(p.options.ExpiryDuration)
defer heartbeat.Stop()
for range heartbeat.C {
if p.IsClosed() {
break
}
p.lock.Lock()
// 刪除workers中過(guò)期的數(shù)據(jù),隊(duì)列是后進(jìn)先出
expiredWorkers := p.workers.retrieveExpiry(p.options.ExpiryDuration)
p.lock.Unlock()
// 這里是否會(huì)有worker被清空了。但是還在執(zhí)行的
for i := range expiredWorkers {
expiredWorkers[i].task <- nil
expiredWorkers[i] = nil
}
// 在廣播一次,如果當(dāng)前沒(méi)有在running的數(shù)據(jù)
if p.Running() == 0 {
p.cond.Broadcast()
}
}
}
- 喚醒的時(shí)候,這時(shí)候清空數(shù)據(jù),應(yīng)該還會(huì)有任務(wù)被執(zhí)行的。
Release
func (p *Pool) Release() {
// 設(shè)置狀態(tài)
atomic.StoreInt32(&p.state, CLOSED)
p.lock.Lock()
// 清空隊(duì)列
p.workers.reset()
p.lock.Unlock()
// 因?yàn)檫€有可能一部分worker還在等待被喚醒,全局廣播
p.cond.Broadcast()
}
總結(jié)
- 整理思路還是很清晰的,submit從隊(duì)列中獲取goworker,并啟動(dòng)goroutine,等待任務(wù)插入。
- 這里過(guò)期的時(shí)候,將w放到了sync.pool中,避免每次過(guò)期,都需要重新創(chuàng)建Pool。
- 其余就是比較正常的,goroutine檢測(cè)task任務(wù),隊(duì)列的存儲(chǔ)等。
大家可以添加我的wx一起探討
我是dandyhzh,關(guān)注我,分析你想要的源碼。碼字不易,點(diǎn)個(gè)小贊,只希望大家能更加明白