如果裸写一个goroutine pool
引言
在上文中,我说到golang的原生http server处理client的connection的时候,每个connection起一个goroutine,这是一个相当粗暴的方法。为了感受更深一点,我们来看一下go的源码。先定义一个最简单的http server如下。
1
2
3
4
5
6
7
8
|
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello there!\n")
}
func main(){
http.HandleFunc("/", myHandler) // 设置访问路由
log.Fatal(http.ListenAndServe(":8080", nil))
}
|
从入口http.ListenAndServe函数跟进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// file: net/http/server.go
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
...
for {
rw, e := l.Accept()
if e != nil {
// error handle
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve()
}
}
|
首先net.Listen
负责监听网络端口,rw, e := l.Accept()
则从网络端口中取出TCP连接,然后go c.server()
则对每一个TCP连接起一个goroutine来处理。我还说到fasthttp这个网络框架性能要比原生的net/http
性能要好,其中一个原因就是因为使用了goroutine pool。那么问题来了,如果要我们自己去实现一个goroutine pool,该怎么去实现呢?我们先来实现一个最简单的。
弱鸡版
golang中的goroutine通过go
来启动,goroutine资源和临时对象池不一样,不能放回去再取出来。所以goroutine应该是一直运行着的。需要的时候就运行,不需要的时候就阻塞,这样对其他的goroutine的调度影响也不是很大。而goroutine的任务可以通过channel来传递就ok了。很简单的弱鸡版本就出来了,如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func Gopool() {
start := time.Now()
wg := new(sync.WaitGroup)
data := make(chan int, 100)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
for _ = range data {
fmt.Println("goroutine:", n, i)
}
}(i)
}
for i := 0; i < 10000; i++ {
data <- i
}
close(data)
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
|
上面的代码中还做了程序运行时间统计。作为对比,下面是一个没有使用pool的版本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func Nopool() {
start := time.Now()
wg := new(sync.WaitGroup)
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
//fmt.Println("goroutine", n)
}(i)
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
|
最后运行时间对比,使用了goroutine pool的代码运行时间约为没有使用pool的代码的2/3。当然这么测试还是略显粗糙了。我们下面使用reflect那篇文章里面介绍的go benchmark testing的方式测试,测试代码如下(去掉了很多无关代码)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package pool
import (
"sync"
"testing"
)
func Gopool() {
wg := new(sync.WaitGroup)
data := make(chan int, 100)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
for _ = range data {
}
}(i)
}
for i := 0; i < 10000; i++ {
data <- i
}
close(data)
wg.Wait()
}
func Nopool() {
wg := new(sync.WaitGroup)
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
}(i)
}
wg.Wait()
}
func BenchmarkGopool(b *testing.B) {
for i := 0; i < b.N; i++ {
Gopool()
}
}
func BenchmarkNopool(b *testing.B) {
for i := 0; i < b.N; i++ {
Nopool()
}
}
|
最终的测试结果如下,使用了goroutine pool的代码执行时间确实更短。
1
2
3
4
5
|
$ go test -bench='.' gopool_test.go
BenchmarkGopool-8 500 2596750 ns/op
BenchmarkNopool-8 500 3604035 ns/op
PASS
|
升级版
对于一个好的线程池,我们往往有更多的需求,一个最迫切的需求是能自定义goroutine运行的函数。函数无非就是函数地址和函数参数。如果要传入的函数形式不一样(形参或者返回值不一样)怎么办?一个比较简单的方法是引入反射。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
type worker struct {
Func interface{}
Args []reflect.Value
}
func main() {
var wg sync.WaitGroup
channels := make(chan worker, 10)
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for ch := range channels {
reflect.ValueOf(ch.Func).Call(ch.Args)
}
}()
}
for i := 0; i < 100; i++ {
wk := worker{
Func: func(x, y int) {
fmt.Println(x + y)
},
Args: []reflect.Value{reflect.ValueOf(i), reflect.ValueOf(i)},
}
channels <- wk
}
close(channels)
wg.Wait()
}
|
但是引入反射又会引入性能问题。本来goroutine pool就是为了解决性能问题,然而现在又引入了新的性能问题。那么怎么办呢?闭包。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
type worker struct {
Func func()
}
func main() {
var wg sync.WaitGroup
channels := make(chan worker, 10)
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for ch := range channels {
//reflect.ValueOf(ch.Func).Call(ch.Args)
ch.Func()
}
}()
}
for i := 0; i < 100; i++ {
j := i
wk := worker{
Func: func() {
fmt.Println(j + j)
},
}
channels <- wk
}
close(channels)
wg.Wait()
}
|
这里值得注意的一点是golang的闭包用不好容易把自己代入坑,而理解闭包一个很关键的点就是对对象的引用而不是复制。这里只是goroutine pool 实现的一个精简版,真正实现的时候还有很多细节需要考虑,比如设置一个stop channel用来停止pool,但是goroutine pool的核心就在于这个地方。
goroutine池和CPU核的关系
那么goroutine池里面goroutine数目和核数有没有关系呢?这个其实要分情况讨论。
1.goroutine池跑不满
这也就意味着channel data里面一有数据就会被goroutine拿走,这样的话当然只能你CPU能调度的过来就行,也就是池子里的goroutine数目和CPU核数是最优的。经测试,确实是这样。
2.channel data有数据阻塞
这意思是说goroutine是不够用的,如果goroutine的运行任务不是CPU密集型的(大部分情况都不是),而只是IO阻塞,这个时候一般goroutine数目在一定范围内是越多越好,当然范围在什么地方就要具体情况具体分析了。
如果裸写一个goroutine pool的更多相关文章
- golang 裸写一个pool池控制协程的大小
这几天深入的研究了一下golang 的协程,读了一个好文 http://mp.weixin.qq.com/s?__biz=MjM5OTcxMzE0MQ==&mid=2653369770& ...
- fasthttp 的 goroutine pool 实现探究
引言 fasthttp是一个非常优秀的web server框架,号称比官方的net/http快10倍以上.fasthttp用了很多黑魔法.俗话说,源码面前,了无秘密,我们今天通过源码来看一看她的gor ...
- goroutine pool,WaitGroup,chan 示例
服务端高并发编程经常需要写很多goroutine来服务每一个连接,如何正确使用goroutine池是又拍云的工程师们需要考虑的问题,今天这篇文章,分享给同样需要使用go语言的小伙伴们. 文/陶克路 本 ...
- 通过 Channel 实现 Goroutine Pool
最近用到了 Go 从 Excel 导数据到服务器内部 用的是 http 请求 但是发现一个问题 从文件读取之后 新开 Goroutine 会无限制新增 导致全部卡在初始化请求 于是乎就卡死了 问题模拟 ...
- python_way ,day11 线程,怎么写一个多线程?,队列,生产者消费者模型,线程锁,缓存(memcache,redis)
python11 1.多线程原理 2.怎么写一个多线程? 3.队列 4.生产者消费者模型 5.线程锁 6.缓存 memcache redis 多线程原理 def f1(arg) print(arg) ...
- 【Python】如何基于Python写一个TCP反向连接后门
首发安全客 如何基于Python写一个TCP反向连接后门 https://www.anquanke.com/post/id/92401 0x0 介绍 在Linux系统做未授权测试,我们须准备一个安全的 ...
- 从头写一个Cucumber测试(二) Cucumber Test
转载:https://yaowenjie.github.io/%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/cucumber-test-part-2 承接上文 前一篇博 ...
- springboot自动装配原理,写一个自己的start
springboot自动装配原理 第一次使用springboot的时候,都感觉很神奇.只要加入一个maven的依赖,写几行配置,就能注入redisTemple,rabbitmqTemple等对象. 这 ...
- 手写一个最迷你的Web服务器
今天我们就仿照Tomcat服务器来手写一个最简单最迷你版的web服务器,仅供学习交流. 1. 在你windows系统盘的F盘下,创建一个文件夹webroot,用来存放前端代码. 2. 代码介绍: ( ...
随机推荐
- python lock, semaphore, event实现线程同步
lock 机制不管你是java, C#, 还是python都是常用的线程同步机制, 相比较C# 的锁机制, python的加锁显得比较简单, 直接调用threading 标准库的lock 就可以了. ...
- 项目群MSP课程最大的特点
1.课程中间让大家去了解和理解项目群管理的知识体系.方法论,更关注大家的个性化需求: 2.课程中间还会有很多练习和讨论,特别是会请到一些业界在实践MSP的客户,进行他们的实践案例分享.所以从知识到实际 ...
- .net中的各种委托(Delegate、Action、Func)
1.Delegate,委托的鼻祖 protected delegate int ClassDelegate(int x, int y);//定义委托类型及参数 static void Main(str ...
- InnoDB存储引擎的总览
InnoDB存储引擎由Innobase Oy公司开发,后被Oracle收购.从MySQL5.5版本开始是默认的存储引擎. InnoDB支持ACID事务.提供行锁设计,支持MVCC.外键,一致性非锁定读 ...
- 手把手教你用Jenkins自动发布dotnet core程序
Jenkins部分 首先,我们要有个Jenkins咯,下载链接:https://jenkins.io/download/ 我们安装官网教程安装好jenkins,安装教程略.... 嗯?不是说好手把手么 ...
- Day5_模块与包(import)(form......import....)
一个文件中定义了很多模块,然后可以再别的文件中调用这几个模块. #导入模块(import) #1,执行源文件 #2,产生以源文件为基础的全局名称空间.
- Android Studio 插件开发详解二:工具类
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78112856 本文出自[赵彦军的博客] 在插件开发过程中,我们按照开发一个正式的项 ...
- day11_jsp/EL/JSTL学习笔记
一.jsp概述 JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术. JSP实际上就是Servlet. JSP这门技术的最大 ...
- Hive入门学习--HIve简介
现在想要应聘大数据分析或者数据挖掘岗位,很多都需要会使用Hive,Mapreduce,Hadoop等这些大数据分析技术.为了充实自己就先从简单的Hive开始吧.接下来的几篇文章是记录我如何入门学习Hi ...
- Python_网页爬虫
import sys import multiprocessing import re import os import urllib.request as lib def craw_links( u ...