fasthttp 协程池实现分析

fasthttp workerpool 源码:

https://github.com/valyala/fasthttp/blob/master/workerpool.go
特点:

  1. workerchan 后进先出
  2. 定时清除workerChan

源码简单分析

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:

  1. 处理http请求的入口
// 来了一个connection
func (wp *workerPool) Serve(c net.Conn) bool {
	ch := wp.getCh() // 获取一个workerChan
	if ch == nil {
		return false
	}
	ch.ch <- c // 往这个workerChan增加一个任务
	return true
}
  1. 获取一个workerChan
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
}
  1. goroutine处理http请求
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()
}
  1. 处理完请求release这个workerchan:
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
}
  1. 当一个ready的workerchan长时间未被使用而被回收
    go在workerpool调用start时,同时启动一个goroutine负责clean
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
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值