goroutine池

324 阅读2分钟

一、为什么要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执行完任务放回池中

流程图

goroutinepool.png

三、具体实现

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

参考:

strikefreedom.top/high-perfor…

segmentfault.com/a/119000002…