go 一步步实现Goroutine Pool
Goroutine Pool架构
超大规模并发的场景下,不加限制的大规模的goroutine可能造成内存暴涨,给机器带来极大的压力,吞吐量下降和处理速度变慢。
而实现一个Goroutine Pool,复用goroutine,减轻runtime的调度压力以及缓解内存压力,依托这些优化,在大规模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 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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
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 51 52 53 54 55 56 57 | // 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
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 | 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
1 2 3 4 5 6 7 8 9 10 | // 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() } |
动态扩容或者缩小容量
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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类
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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是任务本身,它一般为一个函数,在程序中被定义为函数类型:
1 | type functinType func (string) error |
Pool存储Worker,当用户要执行一个task时,首先要得到一个Worker,必须从池中获取,获取到一个Worker后,就开启一个协程去处理,在这个协程中接收任务task和参数。
1 2 3 4 5 6 7 | //创建或得到一个空闲的worker w := p.getWorker()<br> //开协程去处理 w.run() //将任务参数通过信道传递给它 w.sendarg(str) //将任务通过信道传递给它 w.sendTask(task) |
Worker怎么接收task和参数
1 | count定义接收数据的个数,一个Woker必须接收到task和参数才能开始工作。<br>工作完后这个Worker被返回到Pool中,下次还可以复用这个Worker,也就是复用Worker这个实例。 |
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 | 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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实例。
1 2 3 4 5 6 7 8 | // 当前无空闲worker但是Pool还没有满, // 则可以直接新开一个worker执行任务 p.running++ w = &Worker{ pool: p, task: make( chan functinType), str:make( chan string), } |
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package main import ( "Pool/Poolpkg" "fmt" ) func main(){<br> //开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
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | 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
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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 } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
2019-02-28 使用ORM进行前后端数据交互
2019-02-28 Mysql数据库报错:Cannot add or update a child row: a foreign key constraint fails(添加多对多关系)
2019-02-28 Ajax技术使用(一)