fasthttp中的协程池实现

协程池可以控制并行度,复用协程。fasthttp 比 net/http 效率高很多倍的重要原因,就是利用了协程池。实现并不复杂,我们可以参考他的设计,写出高性能的应用。

入口

// server.go

func (s *Server) Serve(ln net.Listener) error {
var lastOverflowErrorTime time.Time
var lastPerIPErrorTime time.Time
var c net.Conn
var err error maxWorkersCount := s.getConcurrency()
s.concurrencyCh = make(chan struct{}, maxWorkersCount)
wp := &workerPool{
WorkerFunc: s.serveConn,
MaxWorkersCount: maxWorkersCount,
LogAllErrors: s.LogAllErrors,
Logger: s.logger(),
}
// break-00
wp.Start() for {
// break-02
if c, err = acceptConn(s, ln, &lastPerIPErrorTime); err != nil {
wp.Stop()
if err == io.EOF {
return nil
}
return err
} // break-03
if !wp.Serve(c) {
s.writeFastError(c, StatusServiceUnavailable,
"The connection cannot be served because Server.Concurrency limit exceeded")
c.Close()
if time.Since(lastOverflowErrorTime) > time.Minute {
s.logger().Printf("The incoming connection cannot be served, because %d concurrent connections are served. "+
"Try increasing Server.Concurrency", maxWorkersCount)
lastOverflowErrorTime = CoarseTimeNow()
} // The current server reached concurrency limit,
// so give other concurrently running servers a chance
// accepting incoming connections on the same address.
//
// There is a hope other servers didn't reach their
// concurrency limits yet :)
time.Sleep(100 * time.Millisecond)
}
c = nil
}
} // 有必要了解一下 workerPool 的结构
type workerPool struct {
// Function for serving server connections.
// It must leave c unclosed.
WorkerFunc func(c net.Conn) error MaxWorkersCount int LogAllErrors bool MaxIdleWorkerDuration time.Duration Logger Logger lock sync.Mutex
workersCount int
mustStop bool ready []*workerChan stopCh chan struct{} workerChanPool sync.Pool
}

goroutine status:

  1. main0: wp.Start()

break-00

// workerpool.go

// 启动一个 goroutine, 每隔一段时间,清理一下 []*workerChan;
// wp.clean() 的操作是 查看最近使用的workerChan, 如果他的最近使用间隔大于某个值,那么把这个workerChan清理了。
func (wp *workerPool) Start() {
if wp.stopCh != nil {
panic("BUG: workerPool already started")
}
wp.stopCh = make(chan struct{})
stopCh := wp.stopCh
go func() {
var scratch []*workerChan
for {
// break-01
wp.clean(&scratch)
select {
case <-stopCh:
return
default:
time.Sleep(wp.getMaxIdleWorkerDuration())
}
}
}()
}

goroutine status:

  1. main0: wp.Start()

  2. g1: for loop to clean idle workerChan

break-01

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
// 这里从队列头部取出超过 最大空闲时间 的workerChan。
// 可以看出,最后使用的workerChan 一定是放回队列尾部的。
for i < n && currentTime.Sub(ready[i].lastUseTime) > maxIdleWorkerDuration {
i++
}
// 把空闲的放入 scratch, 剩余的放回 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() // Notify obsolete workers to stop.
// This notification must be outside the wp.lock, since ch.ch
// may be blocking and may consume a lot of time if many workers
// are located on non-local CPUs.
tmp := *scratch
// 销毁的操作就是向 chan net.Conn 中塞入一个 nil, 后面会看到解释
for i, ch := range tmp {
ch.ch <- nil
tmp[i] = nil
}
}

break-02

acceptConn(s, ln, &lastPerIPErrorTime) 主要处理 ln.Accept(),判断err是否是 Temporary 的,最终返回一个 net.Conn

break-03

// workerpool.go

func (wp *workerPool) Serve(c net.Conn) bool {
// break-04
ch := wp.getCh()
if ch == nil {
return false
}
ch.ch <- c
return true
} type workerChan struct {
lastUseTime time.Time
ch chan net.Conn
}

wp.getCh() 返回一个 *workerChan, 可以看到, workerChan 有一个 ch 属性,参数传入的 net.Conn 直接往里面塞。

break-04

// workerpool.go

func (wp *workerPool) getCh() *workerChan {
var ch *workerChan
createWorker := false wp.lock.Lock()
ready := wp.ready
n := len(ready) - 1
if n < 0 {
// ready 为空,并且总数小于 MaxWorkersCount,那么需要创建新的 workerChan
if wp.workersCount < wp.MaxWorkersCount {
createWorker = true
wp.workersCount++
}
} else {
// 从队列尾部取出一个 workerChan
ch = ready[n]
ready[n] = nil
wp.ready = ready[:n]
}
wp.lock.Unlock() if ch == nil {
if !createWorker {
return nil
}
// 走入创建流程,从 Pool中取出 workerChan
vch := wp.workerChanPool.Get()
if vch == nil {
vch = &workerChan{
ch: make(chan net.Conn, workerChanCap),
}
}
ch = vch.(*workerChan)
// 创建goroutine处理请求,接收一个 chan *workerChan 作为参数
go func() {
// break-05
wp.workerFunc(ch)
wp.workerChanPool.Put(vch)
}()
}
return ch
}

这里我们只看创建的流程。如果ready为空,说明ready被耗尽,并且小于 MaxWorkersCount,那么需要创建新的 workerChan。
创建时,先从 Pool 中取出复用,如果为nil,再创建新的。
可以预测到,这里 wp.workerFunc(ch) 必定包含一个 for 循环,处理 workerChan 中的 net.Conn。

goroutine status:

  1. main0: wp.Start()

  2. g1: for loop to clean idle workerChan

  3. g2: wp.workerFunc(ch) blocks for handling connection

break-05

// workerpool.go

func (wp *workerPool) workerFunc(ch *workerChan) {
var c net.Conn var err error
for c = range ch.ch {
if c == nil {
break
} // 正真的处理请求的函数
if err = wp.WorkerFunc(c); err != nil && err != errHijacked {
errStr := err.Error()
if wp.LogAllErrors || !(strings.Contains(errStr, "broken pipe") ||
strings.Contains(errStr, "reset by peer") ||
strings.Contains(errStr, "i/o timeout")) {
wp.Logger.Printf("error when serving connection %q<->%q: %s", c.LocalAddr(), c.RemoteAddr(), err)
}
}
if err != errHijacked {
c.Close()
}
c = nil // 释放 workerChan
// break-06
if !wp.release(ch) {
break
}
} // 跳出 for range 循环, 意味着 从chan中取得一个 nil,或者 wp.mustStop 被设为了true,这是主动停止的方法。
wp.lock.Lock()
wp.workersCount--
wp.lock.Unlock()
}

for range 不断从 chan net.Conn 中获取连接。大家是否还记得 在 func (wp *workerPool) Serve(c net.Conn) bool 函数中,一个重要操作就是把 accept 到的connection,放入 channel.
最后,需要把当前的 workerChan 释放回 workerPool 的 ready 中。

break-06

func (wp *workerPool) release(ch *workerChan) bool {
ch.lastUseTime = CoarseTimeNow()
wp.lock.Lock()
if wp.mustStop {
wp.lock.Unlock()
return false
}
wp.ready = append(wp.ready, ch)
wp.lock.Unlock()
return true
}

释放操作中,注意到 修改了 ch.lastUseTime , 还记得 clean 操作吗?在 g1 协程中运行着呢。所以最后的运行状态是:

goroutine status:

  1. main0: wp.Start()

  2. g1: for loop to clean idle workerChan

  3. g2: wp.workerFunc(ch) blocks for handling connection

  4. g3: ....

  5. g4: ....

按需增长 goroutine 数量,但是也有一个最大值, 所以并行度是可控的。当请求密集时,一个 worker goroutine 可能会串行处理多个 connection。wokerChan 在 Pool 中被复用,对GC的压力会减小很多。

而对比原生的 net/http 包,并行度不可控(可能不确定,runtime 会有控制? ),goroutine 不可被复用,体现在一个请求一个goroutine, 用完就销毁了,对机器压力更大。

fasthttp中的协程池实现的更多相关文章

  1. golang协程池设计

    Why Pool go自从出生就身带“高并发”的标签,其并发编程就是由groutine实现的,因其消耗资源低,性能高效,开发成本低的特性而被广泛应用到各种场景,例如服务端开发中使用的HTTP服务,在g ...

  2. python中多进程+协程的使用以及为什么要用它

    前面讲了为什么python里推荐用多进程而不是多线程,但是多进程也有其自己的限制:相比线程更加笨重.切换耗时更长,并且在python的多进程下,进程数量不推荐超过CPU核心数(一个进程只有一个GIL, ...

  3. python 协程池和pool.map用法

    一.问题描述 现在有一段代码,需要扫描一个网段内的ip地址,是否可以ping通. 执行起来效率太慢,需要使用协程. #!/usr/bin/env python # -*- coding: utf-8 ...

  4. python3下multiprocessing、threading和gevent性能对比----暨进程池、线程池和协程池性能对比

    python3下multiprocessing.threading和gevent性能对比----暨进程池.线程池和协程池性能对比   标签: python3 / 线程池 / multiprocessi ...

  5. Golang协程池(workpool)实现

    背景 因与工作相关,所以本文中的数据都进行了更改,但逻辑是一样的. 笔者的服务ServerA会请求服务ServerH获取一些数据,但ServerH的接口有个N秒内只能请求M次的限制,并返回false. ...

  6. Python中异步协程的使用方法介绍

    1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞.比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后 ...

  7. lua中的协程

    lua中的协程和线程类似: 1. 协程拥有自己的独立的栈,局部变量,和指令: 2. 所有协程都可以共享全局变量: 3. 协程不能像线程那样并行执行,协程之间需要相互协调执行,同一个时刻只能运行一个协程 ...

  8. Unity中的协程(一)

    这篇文章很不错的问题,推荐阅读英文原版: Introduction to Coroutines Scripting with Coroutines   这篇文章转自:http://blog.csdn. ...

  9. 深入tornado中的协程

    tornado使用了单进程(当然也可以多进程) + 协程 + I/O多路复用的机制,解决了C10K中因为过多的线程(进程)的上下文切换 而导致的cpu资源的浪费. tornado中的I/O多路复用前面 ...

随机推荐

  1. 恶补web之八:jQuery(2)

    jquery中非常重要的部分,就是操作dom的能力: text() - 设置或返回所选元素的文本内容 html() - 设置或返回所选元素的内容(包括html标记) val() - 设置或返回表单字段 ...

  2. python select.select模块通信全过程详解

    要理解select.select模块其实主要就是要理解它的参数, 以及其三个返回值.select()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接 ...

  3. angular1.0 app

    angular 1.0 简单的说一下就是ng启动阶段是 config-->run-->compile/link config阶段是给了ng上下文一个针对constant与provider修 ...

  4. AngularJs 隔离作用域

    初学NG,有诸多的不解,今天看了一篇文章,原文地址:https://segmentfault.com/a/1190000002773689#articleHeader0 ,本文运行的代码也出处此. 里 ...

  5. nginx for Windows

    zt from nginx official site. Known issuesPossible future enhancements Version of nginx for Windows u ...

  6. 关于期权池Option Pools与Vesting:码农创业防身必备法器

    之前又看到饿了么创始人团队纠纷的几篇文章,参考了百科.wiki.36Kr.虎嗅.知乎以及邵亦波老师的文章,对之前一直感兴趣的期权汇编初略总结了下 ,仍觉粗糙,对一些具体操作还是不甚了了,不过感觉在中国 ...

  7. asp.net core ABP模板本地化设置

    ABP的语言本地化设置非常方便,甚至地区图标ABP框架都已经有了. 先看看结果吧. 英文的界面 中文的界面 配置流程如下: 首先在Localization目录下新建一个对应的json文件,里面存放对应 ...

  8. php递归实现无限级分类树

      作者: PHP中文网|标签:PHP 递归 无限级树|2017-5-18 18:09   无限级树状图可以说是无限级栏目的一个显著特征,我们接下来就来看看两种不同的写法. 一.数据库设计 1 2 3 ...

  9. FastDfs上传图片

    1.1. 上传步骤 1.加载配置文件,配置文件中的内容就是tracker服务的地址. 配置文件内容:tracker_server=192.168.25.133:22122 2.创建一个TrackerC ...

  10. Putty连接TPYBorad v102 开发板教程

    第一步:下载Putty软件 http://www.micropython.net.cn/download/tool/3.html 第二步:通过USB数据线将TPYBorad与PC相连 第三步:打开设备 ...