Go语言中的goroutine虽然相对于系统线程来说比较轻量级,但是在高并发量下的goroutine频繁创建和销毁对于性能损耗以及GC来说压力也不小。充分将goroutine复用,减少goroutine的创建/销毁的性能损耗,这便是grpool对goroutine进行池化封装的目的。例如,针对于100W个执行任务,使用goroutine的话需要不停创建并销毁100W个goroutine,而使用grpool也许底层只需要几千个goroutine便能充分复用地执行完成所有任务。经测试,在高并发下grpool的性能比原生的goroutine高出几倍到数百倍!并且随之也极大地降低了内存使用率。

性能测试报告:http://johng.cn/grpool-performance/

方法列表

func Add(f func())
func Jobs() int
func SetExpire(expire int)
func SetSize(size int)
func Size() int
type Pool
func New(expire int, sizes ...int) *Pool
func (p *Pool) Add(f func())
func (p *Pool) Close()
func (p *Pool) Jobs() int
func (p *Pool) SetExpire(expire int)
func (p *Pool) SetSize(size int)
func (p *Pool) Size() int

通过grpool.New方法创建一个goroutine池,并给定池中goroutine的有效时间,单位为,第二个参数为非必需参数,用于限定池中的工作goroutine数量,默认为不限制。需要注意的是,任务可以不停地往池中添加,没有限制,但是工作的goroutine是可以做限制的。我们可以通过Size()方法查询当前的工作goroutine数量,使用Jobs()方法查询当前池中待处理的任务数量。同时,池的大小和goroutine有效期可以通过SetSize和SetExpire方法在运行时进行动态改变。

同时,为便于使用,grpool包提供了默认的goroutine池,直接通过grpool.Add即可往默认的池中添加任务,任务参数必须是一个 func()类型的函数/方法。

使用示例

1、使用默认的goroutine池,限制10个工作goroutine执行1000个任务。

https://gitee.com/johng/gf/blob/master/geg/os/grpool/grpool1.go

package main

import (
"time"
"fmt"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/os/grpool"
) func job() {
time.Sleep(1*time.Second)
} func main() {
grpool.SetSize(10)
for i := 0; i < 1000; i++ {
grpool.Add(job)
}
gtime.SetInterval(2*time.Second, func() bool {
fmt.Println("size:", grpool.Size())
fmt.Println("jobs:", grpool.Jobs())
return true
})
select {}
}

这段程序中的任务函数的功能是sleep 1秒钟,这样便能充分展示出goroutine数量限制功能。其中,我们使用了gtime.SetInterval定时器每隔2秒钟打印出当前默认池中的工作goroutine数量以及待处理的任务数量。

2、我们再来看一个新手经常容易出错的例子

https://gitee.com/johng/gf/blob/master/geg/os/grpool/grpool2.go

package main

import (
"fmt"
"sync"
"gitee.com/johng/gf/g/os/grpool"
) func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
grpool.Add(func() {
fmt.Println(i)
wg.Done()
})
}
wg.Wait()
}

我们这段代码的目的是要顺序地打印出0-9,然而运行后却输出:

10
10
10
10
10
10
10
10
10
10

为什么呢?这里的执行结果无论是采用go关键字来执行还是grpool来执行都是如此。原因是,对于异步线程/协程来讲,函数进行进行异步执行注册时,该函数并未真正开始执行(注册时只在goroutine的栈中保存了变量i的内存地址),而一旦开始执行时函数才会去读取变量i的值,而这个时候变量i的值已经自增到了10。 清楚原因之后,改进方案也很简单了,就是在注册异步执行函数的时候,把当时变量i的值也一并传递获取;或者把当前变量i的值赋值给一个不会改变的临时变量,在函数中使用该临时变量而不是直接使用变量i。

改进后的示例代码如下:

1)、使用go关键字

https://gitee.com/johng/gf/blob/master/geg/os/grpool/grpool3.go

package main

import (
"fmt"
"sync"
) func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func(v int){
fmt.Println(v)
wg.Done()
}(i)
}
wg.Wait()
}

执行后,输出结果为:

9
0
1
2
3
4
5
6
7
8

注意,异步执行时并不会保证按照函数注册时的顺序执行,以下同理。

2)、使用临时变量

https://gitee.com/johng/gf/blob/master/geg/os/grpool/grpool4.go

package main

import (
"fmt"
"sync"
"gitee.com/johng/gf/g/os/grpool"
) func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
v := i
grpool.Add(func() {
fmt.Println(v)
wg.Done()
})
}
wg.Wait()
}

执行后,输出结果为:

9
0
1
2
3
4
5
6
7
8

这里可以看到,使用grpool进行任务注册时,只能使用func()类型的参数,因此无法在任务注册时把变量i的值注册进去,因此只能采用临时变量的形式来传递当前变量i的值。

gf框架之grpool - 高性能的goroutine池的更多相关文章

  1. Java异步NIO框架Netty实现高性能高并发

    原文地址:http://blog.csdn.net/opengl_es/article/details/40979371?utm_source=tuicool&utm_medium=refer ...

  2. Go语言-并发模式-goroutine池实例(work)

    介绍 使用无缓冲的通道来创建一个 goroutine 池,这些 goroutine 执行并控制一组工作,让其并发执行.在这种情况下,使用无缓冲的通道要比随意指定一个缓冲区大小的有缓冲的通道好,因为这个 ...

  3. golang中goroutine池的使用

    1. 概念本质上是生产者.消费者模型可以有效的控制goroutine数量,防止暴涨案例:生成一个随机数,计算该随机数每一个数字相加的和,例如:123:1+2+3=6主协程负责生产数据发送到待处理通道中 ...

  4. Unity 游戏框架搭建 (十九) 简易对象池

    在Unity中我们经常会用到对象池,使用对象池无非就是解决两个问题: 一是减少new时候寻址造成的消耗,该消耗的原因是内存碎片. 二是减少Object.Instantiate时内部进行序列化和反序列化 ...

  5. Dapeng框架-开源高性能分布式微服务框架

    我们公司性质是新零售,公司也有专门的框架组.这群大牛自己开发了一整套分布式微服务框架.我们也在使用这套框架,有很多心得体会. 该框架既Dapeng也!开源github地址:https://github ...

  6. Golang(九)简单 Goroutine 池实现

    0. 前言 最近使用 Golang 写一个并发执行的测试脚本 之前习惯使用 Java,习惯性想先建一个线程池.然后意识到 Golang 没有封装好的线程池 结合之前学习的 Goroutine 原理和 ...

  7. 齐博软件 著名的老牌CMS开源系统 X1.0基于thinkphp开发的高性能免费开源PHP开放平台齐博x1.0基于thinkphp框架开发的高性能免费开源系统 主推圈子 论坛 预定拼团分销商城模块

    齐博X1--标签变量大全 1.网站名称: {$webdb.webname} 2.网址: {$webdb[www_url]} {:get_url('home')} 3.网站SEO关键词: 首页:{$we ...

  8. linux socket高性能服务器处理框架

    这个博客很多东西 http://blog.csdn.net/luozhonghua2014/article/details/37041765   思考一种高性能的服务器处理框架 1.首先需要一个内存池 ...

  9. Goroutine并发调度模型深度解析之手撸一个协程池

    golanggoroutine协程池Groutine Pool高并发 并发(并行),一直以来都是一个编程语言里的核心主题之一,也是被开发者关注最多的话题:Go语言作为一个出道以来就自带 『高并发』光环 ...

随机推荐

  1. RHEL7-openldap安装配置三(客户端自动挂载配置)

    前两篇文章我们配置好了LDAP服务端和LDAP客户端.这篇文章将讲述从LDAP客户机服务器上挂载NFS服务器上共享的目录. 1.LDAP服务器上NFS共享配置 1.1 NFS服务也可以单独搭建在另外一 ...

  2. ios中键盘处理(二)

    设置UIscrollview的背景代码 - (UIImage *) ImageWithColor: (UIColor *) color frame:(CGRect)aFrame { UIGraphic ...

  3. Linux-socket的close和shutdown区别及应用场景

    shutdown的定义 #include<sys/socket.h> int shutdown(int sockfd,int how); how的方式有三种分别是: SHUT_RD(0): ...

  4. 安全删除linux旧内核的方法

    我们在用yum升级系统之后,希望往往会为我们保持旧的内核文件,这样以防在出现硬件或者软件冲突的时候我们能够返回到旧的内核文件继续使用,如果我们想要安全的删除旧的内核文件,可以follow下面的方法. ...

  5. HDUOJ---老人是真饿了

    悼念512汶川大地震遇难同胞——老人是真饿了 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/O ...

  6. 使用requests库发起gbk编码的get请求

    有一个爬虫,查询需要发起get请求,这个网站整个都是gbk编码 如果直接发起请求,是不可以的,因为请求参数q首先用utf8编码转化成bytes,然后将bytes进行URLEncode变为字符串. re ...

  7. iOS 中的各种锁

    在日常开发过程中,为了提升程序运行效率,以及用户体验,我们经常使用多线程.在使用多线程的过程中,难免会遇到资源竞争问题.我们采用锁的机制来确保线程安全. 线程安全 当一个线程访问数据的时候,其他的线程 ...

  8. Redis学习之路(004)- 报错及问题

    在i配置编译的过程中,遇到一下问题: 1. /redis_test: error while loading shared libraries: libhiredis.so.0.13: cannot ...

  9. 一台电脑存放多个git账户的多个rsa秘钥(转)

    如何在一个电脑上存储多个git账户生成的多份rsa秘钥,实现多个账户同时使用配置的情况?今天,不幸又再次遇到这个问题. 问题描述 公司最近在开发一款开源产品,项目被托管在github上,但是公司内部一 ...

  10. Linux提示“libc.so.6: version `GLIBC_2.14' not found”系统的glibc版本太低

    http://www.linuxidc.com/Linux/2017-01/139806.htm http://www.linuxidc.com/Linux/2015-04/116472.htm