https://github.com/valyala/fasthttp/blob/master/workerpool.go
特点:
workerpool struct 定义:
type workerPool struct {
WorkerFunc ServeHandler // 自定义处理connect的方法
MaxWorkersCount int // 最大worker数量
MaxIdleWorkerDuration time.Duration // worker 最大空闲时间,超过就被释放回收
lock sync.Mutex // pool 修改对象时需要用到的互斥锁
workersCount int // 当前worker数
ready []*workerChan // 当前可用的worker队列(协程池),后进先出
stopCh chan struct{} // workerPool 停止标示
workerChanPool sync.Pool // 缓存workerChan的对象池,避免频繁创建
// ....
}
一个goroutine和一个workerChan(channel)绑定,从而实现workerChan控制goroutine的生命周期,类似于下面的模式:
go func(){
for c<- := ch {
// TODO
}
}()
接下来是如何通过workerpool获取可被调度的workerChan:
// 来了一个connection
func (wp *workerPool) Serve(c net.Conn) bool {
ch := wp.getCh() // 获取一个workerChan
if ch == nil {
return false
}
ch.ch <- c // 往这个workerChan增加一个任务
return true
}
func (wp *workerPool) getCh() *workerChan {
var ch *workerChan
createWorker := false
wp.lock.Lock()
ready := wp.ready
n := len(ready) - 1
if n < 0 { // 如果ready队列长度小于0
if wp.workersCount < wp.MaxWorkersCount { // 并且小于最大worker数量
createWorker = true // 允许创建worker
wp.workersCount++
}
} else { // 如果ready队列长度大于0,取最后一个
ch = ready[n]
ready[n] = nil
wp.ready = ready[:n] // 取出后将最后一个pop掉
}
wp.lock.Unlock()
if ch == nil {
if !createWorker { // ready 队列长度小于0,并且大于最大worker数量,直接拒绝处理
return nil
}
vch := wp.workerChanPool.Get() // 从对象池中取一个,避免重复创建
if vch == nil { // 没有就创建
vch = &workerChan{
ch: make(chan net.Conn, workerChanCap),
}
}
ch = vch.(*workerChan)
go func() { // 开启一个goroutine,并且开始for消费绑定的channel(请求)
wp.workerFunc(ch)
wp.workerChanPool.Put(vch)
}()
}
return ch
}
func (wp *workerPool) workerFunc(ch *workerChan) {
var c net.Conn
var err error
for c = range ch.ch {
if c == nil { // 如果被检查到超过最大时间,会写入一个nil标示goroutine需要完成和销毁
break
}
if err = wp.WorkerFunc(c); err != nil && err != errHijacked {
errStr := err.Error()
if wp.LogAllErrors || !(strings.Contains(errStr, "broken pipe") ||
// ... 省略
}
}
// 省略
if !wp.release(ch) { // 最终 release 这个channel,将其重新放进ready中
break
}
}
wp.lock.Lock()
wp.workersCount--
wp.lock.Unlock()
}
func (wp *workerPool) release(ch *workerChan) bool {
ch.lastUseTime = time.Now()
wp.lock.Lock()
if wp.mustStop {
wp.lock.Unlock()
return false
}
wp.ready = append(wp.ready, ch) // 重新放入ready队列
wp.lock.Unlock()
return true
}
func (wp *workerPool) Start() {
wp.stopCh = make(chan struct{}) //
stopCh := wp.stopCh
go func() {
var scratch []*workerChan
for {
wp.clean(&scratch) // 清理
select {
case <-stopCh: // workerPool 停止
return
default: // 每隔一段时间清理一次
time.Sleep(wp.getMaxIdleWorkerDuration())
}
}
}()
}
func (wp *workerPool) clean(scratch *[]*workerChan) {
maxIdleWorkerDuration := wp.getMaxIdleWorkerDuration()
// Clean least recently used workers if they didn't serve connections
// for more than maxIdleWorkerDuration.
currentTime := time.Now()
wp.lock.Lock()
ready := wp.ready
n := len(ready)
i := 0
for i < n && currentTime.Sub(ready[i].lastUseTime) > maxIdleWorkerDuration { // 清理0~m
i++
}
// 更新ready列表
*scratch = append((*scratch)[:0], ready[:i]...)
if i > 0 {
m := copy(ready, ready[i:])
for i = m; i < n; i++ {
ready[i] = nil
}
wp.ready = ready[:m]
}
wp.lock.Unlock()
tmp := *scratch
for i, ch := range tmp {
ch.ch <- nil // 通知goroutine完成
tmp[i] = nil
}
}