• go 一步步实现Goroutine Pool


     Goroutine Pool架构

    超大规模并发的场景下,不加限制的大规模的goroutine可能造成内存暴涨,给机器带来极大的压力,吞吐量下降和处理速度变慢。

    而实现一个Goroutine Pool,复用goroutine,减轻runtime的调度压力以及缓解内存压力,依托这些优化,在大规模goroutine并发的场景下可以极大地提高并发性能。

    Pool类型

    type Pool struct {
    	// capacity of the pool.
    	//capacity是该Pool的容量,也就是开启worker数量的上限,每一个worker需要一个goroutine去执行;
    	//worker类型为任务类。
    	capacity int32
    	// running is the number of the currently running goroutines.
    	//running是当前正在执行任务的worker数量
    	running int32
    	// expiryDuration set the expired time (second) of every worker.
    	//expiryDuration是worker的过期时长,在空闲队列中的worker的最新一次运行时间与当前时间之差如果大于这个值则表示已过期,定时清理任务会清理掉这个worker;
    	expiryDuration time.Duration
    	// workers is a slice that store the available workers.
    	//任务队列
    	workers []*Worker
    	// release is used to notice the pool to closed itself.
    	//当关闭该Pool支持通知所有worker退出运行以防goroutine泄露
    	release chan sig
    	// lock for synchronous operation
    	//用以支持Pool的同步操作
    	lock sync.Mutex
    	//once用在确保Pool关闭操作只会执行一次
    	once sync.Once
    }

    初始化Pool

    // NewPool generates a instance of ants pool
    func NewPool(size, expiry int) (*Pool, error) {
    	if size <= 0 {
    		return nil, errors.New("Pool Size <0,not Create")
    	}
    	p := &Pool{
    		capacity:       int32(size),
    		release:        make(chan sig, 1),
    		expiryDuration: time.Duration(expiry) * time.Second,
    		running:		0,
    	}
    	// 启动定期清理过期worker任务,独立goroutine运行,
    	// 进一步节省系统资源
    	p.monitorAndClear()
    	return p, nil
    }

    获取Worker

    // getWorker returns a available worker to run the tasks.
    func (p *Pool) getWorker() *Worker {
    	var w *Worker
    	// 标志,表示当前运行的worker数量是否已达容量上限
    	waiting := false
    	// 涉及从workers队列取可用worker,需要加锁
    	p.lock.Lock()
    	workers := p.workers
    	n := len(workers) - 1
    	fmt.Println("空闲worker数量:",n+1)
    	fmt.Println("协程池现在运行的worker数量:",p.running)
    	// 当前worker队列为空(无空闲worker)
    	if n < 0 {
    		//没有空闲的worker有两种可能:
    		//1.运行的worker超出了pool容量
    		//2.当前是空pool,从未往pool添加任务或者一段时间内没有任务添加,被定期清除
    		// 运行worker数目已达到该Pool的容量上限,置等待标志
    		if p.running >= p.capacity {
    			//print("超过上限")
    			waiting = true
    		} else {
    			// 当前无空闲worker但是Pool还没有满,
    			// 则可以直接新开一个worker执行任务
    			p.running++
    			w = &Worker{
    				pool: p,
    				task: make(chan functinType),
    				str:make(chan string),
    			}
    		}
    		// 有空闲worker,从队列尾部取出一个使用
    	} else {
    		//<-p.freeSignal
    		w = workers[n]
    		workers[n] = nil
    		p.workers = workers[:n]
    		p.running++
    	}
    	// 判断是否有worker可用结束,解锁
    	p.lock.Unlock()
    	if waiting {
    		//当一个任务执行完以后会添加到池中,有了空闲的任务就可以继续执行:
    		// 阻塞等待直到有空闲worker
    		for len(p.workers) == 0{
    			continue
    		}
    		p.lock.Lock()
    		workers = p.workers
    		l := len(workers) - 1
    		w = workers[l]
    		workers[l] = nil
    		p.workers = workers[:l]
    		p.running++
    		p.lock.Unlock()
    	}
    	return w
    }

    定期清理过期Worker

    func (p *Pool) monitorAndClear() {
    	go func() {
    		for {
    			// 周期性循环检查过期worker并清理
    			time.Sleep(p.expiryDuration)
    			currentTime := time.Now()
    			p.lock.Lock()
    			idleWorkers := p.workers
    			n := 0
    			for i, w := range idleWorkers {
    				// 计算当前时间减去该worker的最后运行时间之差是否符合过期时长
    				if currentTime.Sub(w.recycleTime) <= p.expiryDuration {
    					break
    				}
    				n = i
    				w.stop()
    				idleWorkers[i] = nil
    			}
    			if n > 0 {
    				n++
    				p.workers = idleWorkers[n:]
    			}
    			p.lock.Unlock()
    		}
    	}()
    }

    复用Worker

    // putWorker puts a worker back into free pool, recycling the goroutines.
    func (p *Pool) putWorker(worker *Worker) {
    	// 写入回收时间,亦即该worker的最后运行时间
    	worker.recycleTime = time.Now()
    	p.lock.Lock()
    	p.running --
    	p.workers = append(p.workers, worker)
    	p.lock.Unlock()
    
    }

    动态扩容或者缩小容量

    // ReSize change the capacity of this pool
    func (p *Pool) ReSize(size int) {
    	cap := int(p.capacity)
    	if size <  cap{
    		diff := cap - size
    		for i := 0; i < diff; i++ {
    			p.getWorker().stop()
    		}
    	} else if size == cap {
    		return
    	}
    	atomic.StoreInt32(&p.capacity, int32(size))
    } 

    提交Worker

    // Submit submit a task to pool
    func (p *Pool) Submit(task functinType,str string) error {
    	if len(p.release) > 0 {
    		return errors.New("Pool is Close")
    	}
    	//创建或得到一个空闲的worker
    	w := p.getWorker()
    	w.run()
    	//将任务参数通过信道传递给它
    	w.sendarg(str)
    	//将任务通过信道传递给它
    	w.sendTask(task)
    	return nil
    }
    

      

    Worker类

    package Poolpkg
    
    import (
    	"sync/atomic"
    	"time"
    )
    
    type functinType func(string) error
    
    
    // Worker is the actual executor who runs the tasks,
    // it starts a goroutine that accepts tasks and
    // performs function calls.
    type Worker struct {
    	// pool who owns this worker.
    	pool *Pool
    	// task is a job should be done.
    	task chan functinType
    	// recycleTime will be update when putting a worker back into queue.
    	recycleTime time.Time
    
    	str chan string
    }
    
    // run starts a goroutine to repeat the process
    // that performs the function calls.
    func (w *Worker) run() {
    
    	go func() {
    		//监听任务列表,一旦有任务立马取出运行
    		count := 1
    		var str string
    		var f functinType
    		for count <=2{
    			select {
    			case str_temp, ok := <- w.str:
    				if !ok {
    					return
    				}
    				count ++
    				str = str_temp
    			case f_temp, ok := <-w.task:
    				if !ok {
    					//如果接收到关闭
    					atomic.AddInt32(&w.pool.running, -1)
    					close(w.task)
    					return
    				}
    				count  ++
    				f = f_temp
    			}
    		}
    		err := f(str)
    		if err != nil{
    			//fmt.Println("执行任务失败")
    		}
    		//回收复用
    		w.pool.putWorker(w)
    		return
    	}()
    }
    
    // stop this worker.
    func (w *Worker) stop() {
    	w.sendTask(nil)
    	close(w.str)
    }
    
    // sendTask sends a task to this worker.
    func (w *Worker) sendTask(task functinType) {
    	w.task <- task
    }
    
    func (w *Worker) sendarg(str string) {
    	w.str <- str
    }
    

      

    总结和实践

    怎么理解Woreker,task、Pool的关系

    Woker类型其实就是task的载体,Worker类型有两个很重要的参数:

    task chan functinType:用来是传递task。
    str chan string:用来传递task所需的参数。

    task是任务本身,它一般为一个函数,在程序中被定义为函数类型:

    type functinType func(string) error

    Pool存储Worker,当用户要执行一个task时,首先要得到一个Worker,必须从池中获取,获取到一个Worker后,就开启一个协程去处理,在这个协程中接收任务task和参数。

    //创建或得到一个空闲的worker
    w := p.getWorker()
    //开协程去处理 w.run() //将任务参数通过信道传递给它 w.sendarg(str) //将任务通过信道传递给它 w.sendTask(task)

    Worker怎么接收task和参数

    count定义接收数据的个数,一个Woker必须接收到task和参数才能开始工作。
    工作完后这个Worker被返回到Pool中,下次还可以复用这个Worker,也就是复用Worker这个实例。
    go func() {
    		//监听任务列表,一旦有任务立马取出运行
    		count := 1
    		var str string
    		var f functinType
    		for count <=2{
    			select {
    			case str_temp, ok := <- w.str:
    				if !ok {
    					return
    				}
    				count ++
    				str = str_temp
    			case f_temp, ok := <-w.task:
    				if !ok {
    					//如果接收到关闭
    					atomic.AddInt32(&w.pool.running, -1)
    					close(w.task)
    					return
    				}
    				count  ++
    				f = f_temp
    			}
    		}
    		err := f(str)
    		if err != nil{
    			//fmt.Println("执行任务失败")
    		}
    		//回收复用
    		w.pool.putWorker(w)
    		return
    	}()

    Pool怎么处理用户提交task获取Worker的请求

    1.先得到Pool池中空闲Worker的数量,然后判断

    2.如果小于零,则表示池中没有空闲的Worker,这里有两种原因:

    • 1.运行的Worker数量超过了Pool容量,当用户获取Worker的请求数量激增,池中大多数Worker都是执行完任务的Worker重新添加到池中的,返回的Worker跟不上激增的需求。
    • 2.当前是空pool,从未往pool添加任务或者一段时间内没有Worker任务运行,被定期清除。

    3.如果大于或者等于零,有空闲的Worker直接从池中获取最后一个Worker。

    4.如果是第二种的第一种情况,则阻塞等待池中有空闲的Worker。

    if waiting {
    		//当一个任务执行完以后会添加到池中,有了空闲的任务就可以继续执行:
    		// 阻塞等待直到有空闲worker
    		for len(p.workers) == 0{
    			continue
    		}
    		p.lock.Lock()
    		workers = p.workers
    		l := len(workers) - 1
    		w = workers[l]
    		workers[l] = nil
    		p.workers = workers[:l]
    		p.running++
    		p.lock.Unlock()
    	}
    

    5.如果是第二种的第二种情况,直接创建一个Worker实例。

    // 当前无空闲worker但是Pool还没有满,
    // 则可以直接新开一个worker执行任务
    p.running++
    w = &Worker{
    	pool: p,
    	task: make(chan functinType),
    	str:make(chan string),
    }

    测试

    package main
    
    import (
    	"Pool/Poolpkg"
    	"fmt"
    )
    
    func main(){
         //开20个大小的Pool池,过期清除时间5分钟 Pool,err := Poolpkg.NewPool(20,5) i :=0 for i < 50 { err = Pool.Submit(Print_Test1,"并发测试!") if err != nil{ fmt.Println(err) } i++ } }

    源码

    Pool

    package Poolpkg
    
    import (
    	"errors"
    	"fmt"
    	"sync"
    	"sync/atomic"
    	"time"
    )
    
    type sig struct{}
    
    
    
    // Pool accept the tasks from client,it limits the total
    // of goroutines to a given number by recycling goroutines.
    type Pool struct {
    	// capacity of the pool.
    	//capacity是该Pool的容量,也就是开启worker数量的上限,每一个worker需要一个goroutine去执行;
    	//worker类型为任务类。
    	capacity int32
    	// running is the number of the currently running goroutines.
    	//running是当前正在执行任务的worker数量
    	running int32
    	// expiryDuration set the expired time (second) of every worker.
    	//expiryDuration是worker的过期时长,在空闲队列中的worker的最新一次运行时间与当前时间之差如果大于这个值则表示已过期,定时清理任务会清理掉这个worker;
    	expiryDuration time.Duration
    	// workers is a slice that store the available workers.
    	//任务队列
    	workers []*Worker
    	// release is used to notice the pool to closed itself.
    	//当关闭该Pool支持通知所有worker退出运行以防goroutine泄露
    	release chan sig
    	// lock for synchronous operation
    	//用以支持Pool的同步操作
    	lock sync.Mutex
    	//once用在确保Pool关闭操作只会执行一次
    	once sync.Once
    }
    
    // NewPool generates a instance of ants pool
    func NewPool(size, expiry int) (*Pool, error) {
    	if size <= 0 {
    		return nil, errors.New("Pool Size <0,not Create")
    	}
    	p := &Pool{
    		capacity:       int32(size),
    		release:        make(chan sig, 1),
    		expiryDuration: time.Duration(expiry) * time.Second,
    		running:		0,
    	}
    	// 启动定期清理过期worker任务,独立goroutine运行,
    	// 进一步节省系统资源
    	p.monitorAndClear()
    	return p, nil
    }
    
    // Submit submit a task to pool
    func (p *Pool) Submit(task functinType,str string) error {
    	if len(p.release) > 0 {
    		return errors.New("Pool is Close")
    	}
    	//创建或得到一个空闲的worker
    	w := p.getWorker()
    	w.run()
    	//将任务参数通过信道传递给它
    	w.sendarg(str)
    	//将任务通过信道传递给它
    	w.sendTask(task)
    	return nil
    }
    
    // getWorker returns a available worker to run the tasks.
    func (p *Pool) getWorker() *Worker {
    	var w *Worker
    	// 标志,表示当前运行的worker数量是否已达容量上限
    	waiting := false
    	// 涉及从workers队列取可用worker,需要加锁
    	p.lock.Lock()
    	workers := p.workers
    	n := len(workers) - 1
    	fmt.Println("空闲worker数量:",n+1)
    	fmt.Println("协程池现在运行的worker数量:",p.running)
    	// 当前worker队列为空(无空闲worker)
    	if n < 0 {
    		//没有空闲的worker有两种可能:
    		//1.运行的worker超出了pool容量
    		//2.当前是空pool,从未往pool添加任务或者一段时间内没有任务添加,被定期清除
    		// 运行worker数目已达到该Pool的容量上限,置等待标志
    		if p.running >= p.capacity {
    			//print("超过上限")
    			waiting = true
    		} else {
    			// 当前无空闲worker但是Pool还没有满,
    			// 则可以直接新开一个worker执行任务
    			p.running++
    			w = &Worker{
    				pool: p,
    				task: make(chan functinType),
    				str:make(chan string),
    			}
    		}
    		// 有空闲worker,从队列尾部取出一个使用
    	} else {
    		//<-p.freeSignal
    		w = workers[n]
    		workers[n] = nil
    		p.workers = workers[:n]
    		p.running++
    	}
    	// 判断是否有worker可用结束,解锁
    	p.lock.Unlock()
    	if waiting {
    		//当一个任务执行完以后会添加到池中,有了空闲的任务就可以继续执行:
    		// 阻塞等待直到有空闲worker
    		for len(p.workers) == 0{
    			continue
    		}
    		p.lock.Lock()
    		workers = p.workers
    		l := len(workers) - 1
    		w = workers[l]
    		workers[l] = nil
    		p.workers = workers[:l]
    		p.running++
    		p.lock.Unlock()
    	}
    	return w
    }
    
    //定期清理过期Worker
    func (p *Pool) monitorAndClear() {
    	go func() {
    		for {
    			// 周期性循环检查过期worker并清理
    			time.Sleep(p.expiryDuration)
    			currentTime := time.Now()
    			p.lock.Lock()
    			idleWorkers := p.workers
    			n := 0
    			for i, w := range idleWorkers {
    				// 计算当前时间减去该worker的最后运行时间之差是否符合过期时长
    				if currentTime.Sub(w.recycleTime) <= p.expiryDuration {
    					break
    				}
    				n = i
    				w.stop()
    				idleWorkers[i] = nil
    				p.running--
    			}
    			if n > 0 {
    				n++
    				p.workers = idleWorkers[n:]
    			}
    			p.lock.Unlock()
    		}
    	}()
    }
    
    //Worker回收(goroutine复用)
    // putWorker puts a worker back into free pool, recycling the goroutines.
    func (p *Pool) putWorker(worker *Worker) {
    	// 写入回收时间,亦即该worker的最后运行时间
    	worker.recycleTime = time.Now()
    	p.lock.Lock()
    	p.running --
    	p.workers = append(p.workers, worker)
    	p.lock.Unlock()
    
    }
    
    //动态扩容或者缩小池容量
    // ReSize change the capacity of this pool
    func (p *Pool) ReSize(size int) {
    	cap := int(p.capacity)
    	if size <  cap{
    		diff := cap - size
    		for i := 0; i < diff; i++ {
    			p.getWorker().stop()
    		}
    	} else if size == cap {
    		return
    	}
    	atomic.StoreInt32(&p.capacity, int32(size))
    }
    

    Woker

    package Poolpkg
    
    import (
    	"sync/atomic"
    	"time"
    )
    
    type functinType func(string) error
    
    
    // Worker is the actual executor who runs the tasks,
    // it starts a goroutine that accepts tasks and
    // performs function calls.
    type Worker struct {
    	// pool who owns this worker.
    	pool *Pool
    	// task is a job should be done.
    	task chan functinType
    	// recycleTime will be update when putting a worker back into queue.
    	recycleTime time.Time
    
    	str chan string
    }
    
    // run starts a goroutine to repeat the process
    // that performs the function calls.
    func (w *Worker) run() {
    
    	go func() {
    		//监听任务列表,一旦有任务立马取出运行
    		count := 1
    		var str string
    		var f functinType
    		for count <=2{
    			select {
    			case str_temp, ok := <- w.str:
    				if !ok {
    					return
    				}
    				count ++
    				str = str_temp
    			case f_temp, ok := <-w.task:
    				if !ok {
    					//如果接收到关闭
    					atomic.AddInt32(&w.pool.running, -1)
    					close(w.task)
    					return
    				}
    				count  ++
    				f = f_temp
    			}
    		}
    		err := f(str)
    		if err != nil{
    			//fmt.Println("执行任务失败")
    		}
    		//回收复用
    		w.pool.putWorker(w)
    		return
    	}()
    }
    
    // stop this worker.
    func (w *Worker) stop() {
    	w.sendTask(nil)
    	close(w.str)
    }
    
    // sendTask sends a task to this worker.
    func (w *Worker) sendTask(task functinType) {
    	w.task <- task
    }
    
    func (w *Worker) sendarg(str string) {
    	w.str <- str
    }
    

     

  • 相关阅读:
    windown 下最简单的安装mysql方式
    mac 重置mysql密码
    开发过程中用到的软件
    Springboot 热部署问题。亲测可用。
    时间转换~
    java 流转换BASE64的一些问题
    SpringMvc 使用Thumbnails压缩图片
    SpringMVC Get请求传集合,前端"异步"下载excel 附SpringMVC 后台接受集合
    Mac 笔记本 开发日记
    RabbitMQ入门:路由(Routing)
  • 原文地址:https://www.cnblogs.com/-wenli/p/12378699.html
Copyright © 2020-2023  润新知