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. apt-get Ubuntu本地ISO镜像入源

    转自http://blog.csdn.net/binchel/article/details/21486999 在没有网络的情况下,本地镜像源不实为一个上等的权宜之计! 目前linux的两大主流包管理 ...

  2. CDH安装系统环境准备——系统版本和安装包下载地址指南

    由于Hadoop深受客户欢迎,许多公司都推出了各自版本的Hadoop,也有一些公司则围绕Hadoop开发产品.在Hadoop生态系统中,规模最大.知名度最高的公司则是Cloudera.接下来的日子里, ...

  3. JFace dailog button事件中刷新透视图异常 Trying to execute the disabled command org.eclipse.ui.window.closePerspective

    报错的代码为 protected void buttonPressed(int buttonId) { Display.getDefault().syncExec(new Runnable() { p ...

  4. ORACLE复杂查询之连接查询

    一.传统的连接查询 1.交叉连接:返回笛卡尔积 WHERE中限定查询条件,可以预先过滤掉掉不符合条件的记录,返回的只是两个表中剩余记录(符合条件的记录)的笛卡尔积. 2.内连接:参与连接的表地位平等, ...

  5. CSS的应用下

    样式继承: 就是父类的颜色如果变了,子类下的div(或者其他属性)会继承父类的. 参考代码: <!DOCTYPE html> <html lang="en"> ...

  6. SQL基本语句的优化10个原则

    原则一:尽量避免在列上进行运算,这样会导致索引失效. 例如: ; 优化: SELECT * FROM table WHERE d >= '2011-01-01'; 原则二:使用JOIN时,应该用 ...

  7. 谣传QQ被黑客DDOS攻击,那么Python如何实现呢?

    于2018-5-10日晚 网络流传黑客DDOS攻击了QQ服务器,导致大家聊天发送内容时出现感叹号.我们都知道一般情况下出现感叹号都是你的网络不稳定,或者...别人已经删除你了.然而昨晚很奇怪,发出的内 ...

  8. MySQL中的外键约束

  9. 重载new和delete来检测内存泄漏

    重载new和delete来检测内存泄漏 1. 简述 内存泄漏属于资源泄漏的一种,百度百科将内存泄漏分为四种:常发性内存泄漏.偶发性内存泄漏.一次性内存泄漏和隐式内存泄漏.    常发性指:内存泄漏的代 ...

  10. python_集合

    >>> import random>>> import time>>> x=list(range(10000))>>> y=se ...