一、为什么要goroutine池
目的:减轻runtime的调度压力以及缓解内存压力
分析:
1)即使每个goroutine只有2kb的内存,但数量过多也会占用大量内存,对GC会造成很大的负担(stw)
2)runtime和gc也都是goroutine
二、设计思路
goroutine的数量限制和重用
//1.初始化一个goroutine池
//2.推送任务到goRoutine池
//3.如果池中有空闲worker,取出执行任务
//4.如果池中没有空闲worker,是否达到池的最大容量?
//5.如果没有达到则新创建一个worker,执行任务
//6.如果达到了最大容量,则等待空闲worker,取到之后执行任务(阻塞情况下)
//7.worker执行完任务放回池中
流程图:
三、具体实现
3.1 定义结构
var (
count int32
cleanInterval = 15 * time.Second
)
type Task struct {
Handler func(v ...interface{})
Params []interface{}
}
type Pool struct {
cap uint32 //goroutine池的容量
workers []*Worker //空闲的worker
runningWorkerNum uint32 //工作中的worker数量
quit chan struct{} //工作池的状态
isBlocking bool //工作池是否为阻塞模式
sync.RWMutex
}
type Worker struct {
pool *Pool //worker所属pool的地址
chTask chan *Task //接收任务
lastRuntime time.Time //上次结束运行的时间
count int32 //为了进行测试加的字段(所创建的第几个worker count是全局变量)
}
3.2 初始化goroutine池
//初始化pool,并启动定时清理任务
func NewPool(cap uint32, isBlocking bool) *Pool {
if cap < 1 {
return nil
}
p := &Pool{
cap: cap,
workers: make([]*Worker, 0, cap),
runningWorkerNum: 0,
isBlocking: isBlocking,
quit: make(chan struct{}, 1),
}
go p.cleanWorker()
return p
}
//定期清理过期的worker
func (p *Pool) cleanWorker() {
expiredTime := cleanInterval
for {
time.Sleep(cleanInterval)
curTime := time.Now()
n := 0
p.Lock()
if len(p.quit) > 0 {
for _, worker := range p.workers {
worker.close()
}
p.Unlock()
return
}
for _, worker := range p.workers {
if worker.lastRuntime.Add(expiredTime).After(curTime) {
break
}
//清理
worker.close()
n++
}
if n > 0 {
p.workers = p.workers[n:]
}
p.Unlock()
}
}
3.3 任务投递
//推送任务到pool
func (p *Pool) Put(task *Task) error {
if len(p.quit) > 0 {
return errors.New("pool is stoped")
}
w, err := p.getWorker()
if err != nil {
return err
}
w.chTask <- task
return nil
}
//获取可用的worker
func (p *Pool) getWorker() (*Worker, error) {
w := &Worker{}
p.Lock()
//如果有空闲的worker,取最后一个
if len(p.workers) > 0 {
w = p.workers[len(p.workers)-1]
p.workers = p.workers[:len(p.workers)-1]
p.Unlock()
return w, nil
}
p.Unlock()
//如果没有空闲的worker
//工作池容量没有用完,新启动一个worker
if p.getRunningWorkerNum() < p.cap {
p.icrRunningworkerNum()
count = atomic.AddInt32(&count, 1)
w = &Worker{
pool: p,
chTask: make(chan *Task),
count: count,
}
fmt.Println("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", w.count)
w.run()
return w, nil
}
//工作池容量用完了
//判断工作池是否为阻塞模式
if p.isBlocking == false { // 非阻塞
return nil, errors.New("pool worker is not free")
} else { //阻塞等待可用worker
for {
p.Lock()
if len(p.workers) == 0 {
p.Unlock()
time.Sleep(time.Duration(6) * time.Millisecond)
continue
}
w = p.workers[len(p.workers)-1]
p.workers = p.workers[:len(p.workers)-1]
p.Unlock()
break
}
}
return w, nil
}
3.4 任务执行
//处理任务
func (w *Worker) run() {
go func() {
defer w.pool.decRunningworkerNum()
for {
select {
case task, ok := <-w.chTask:
if !ok {
return
}
task.Handler(task.Params, w.count)
//记录worker结束运行的时间,以便回收
w.lastRuntime = time.Now()
//把worker放回工作池
w.pool.Lock()
w.pool.workers = append(w.pool.workers, w)
w.pool.Unlock()
}
}
}()
}
3.5 伸缩池的大小
func (p *Pool) Resize(size uint32) error {
if size < 0 {
return errors.New("size illegal")
}
atomic.StoreUint32(&p.cap, size)
n := len(p.workers) - int(p.getCap())
//缩容
if n > 0 {
for i := 0; i < n; i++ {
worker, err := p.getWorker()
if err != nil {
fmt.Println("err:", err)
return err
}
worker.close()
}
}
return nil
}
3.6 关闭和清理
//关闭pool,并做清理工作
func (p *Pool) Close() error {
p.quit <- struct{}{}
return nil
}
//close
func (w *Worker) close() {
close(w.chTask)
w = nil
}
3.7 其它原子操作
//获取pool的容量
func (p *Pool) getCap() uint32 {
return atomic.LoadUint32(&p.cap)
}
//获取正在运行的worker数量
func (p *Pool) getRunningWorkerNum() uint32 {
return atomic.LoadUint32(&p.runningWorkerNum)
}
//自增正在运行的worker数量
func (p *Pool) icrRunningworkerNum() uint32 {
return atomic.AddUint32(&p.runningWorkerNum, 1)
}
//自减正在运行的worker数量
func (p *Pool) decRunningworkerNum() uint32 {
return atomic.AddUint32(&p.runningWorkerNum, ^uint32(0))
}
3.8 main函数测试
func main() {
pool := NewPool(10, true)
for i := 0; i < 20; i++ {
t := i
err := pool.Put(&Task{
Handler: func(v ...interface{}) {
fmt.Println(v, "handler...")
},
Params: []interface{}{i},
})
if err != nil {
fmt.Println("error:", t, err)
}
}
time.Sleep(2 * time.Second)
fmt.Println("start resize.........")
_ = pool.Resize(0)
time.Sleep(2 * time.Second)
for i := 20; i < 100; i++ {
t := i
err := pool.Put(&Task{
Handler: func(v ...interface{}) {
fmt.Println(v, "handler...")
//time.Sleep(50 * time.Millisecond)
},
Params: []interface{}{i},
})
if err != nil {
fmt.Println("error:", t, err)
}
}
fmt.Println(len(pool.workers))
//pool.Close()
for {
}
}
3.9 另一种实现
package main
import (
"errors"
"fmt"
"log"
"sync"
"sync/atomic"
"time"
)
const (
STOPED = 0
RUNNING = 1
)
type Task struct {
Handler func(v ...interface{})
Params []interface{}
}
type Pool struct {
cap uint32 //goroutine池的容量
runningWorkerNum uint32 //工作中的worker数量
chtask chan *Task //存储任务的channel
state int //goroutine池的状态(running stoped)
isBlocking bool //工作池是否为阻塞模式
sync.RWMutex
}
// 创建工作池
func newPool(cap uint32, isBlocking bool) *Pool {
return &Pool{
cap: cap,
chtask: make(chan *Task, cap),
isBlocking: isBlocking,
state: RUNNING,
}
}
// worker开始工作
func (p *Pool) run() {
p.icrRunningworkerNum()
go func() {
defer func() {
p.decRunningworkerNum()
if r := recover(); r != nil {
log.Println("worker panic... ", r)
}
}()
for {
select {
case task, ok := <-p.chtask:
if !ok {
return
}
task.Handler(task.Params...)
}
}
}()
}
// 投递任务
func (p *Pool) Put(task *Task) error {
if p.state == STOPED {
return errors.New("pool is stoped")
}
p.Lock()
//如果工作池没满
if p.getRunningworkerNum() < p.getCap() {
//创建一个worker
p.run()
}
p.Unlock()
p.chtask <- task
return nil
}
//close
func (p *Pool) Close() {
p.Lock()
p.state = STOPED
p.Unlock()
if len(p.chtask) > 0 {
time.Sleep(1 * time.Second)
}
close(p.chtask)
}
func (p *Pool) getCap() uint32 {
return p.cap
}
func (p *Pool) getRunningworkerNum() uint32 {
return atomic.LoadUint32(&p.runningWorkerNum)
}
func (p *Pool) icrRunningworkerNum() uint32 {
return atomic.AddUint32(&p.runningWorkerNum, 1)
}
func (p *Pool) decRunningworkerNum() uint32 {
return atomic.AddUint32(&p.runningWorkerNum, ^uint32(0))
}
func demo(v ...interface{}) {
fmt.Println("v:", v)
}
func main() {
pool := newPool(10, false)
for i := 0; i < 20; i++ {
pool.Put(&Task{
Handler: demo,
Params: []interface{}{i, pool.getRunningworkerNum()},
})
}
time.Sleep(1 * time.Second)
pool.Close()
}
四、benchmark
参考: