如何高效运用Go ants pool协程池实现并发编程?

摘要:概述 使用 Go 开发并发程序很容易,一个 go 关键字就可以启动协程处理任务。Go 创建一个 goroutine 只需要 2K 内存空间,并且 go 协程上下文信息仅存储在两个寄存器中,对于 Go 运行时来说,切换上下文特别快。 不过凡事
概述 使用 Go 开发并发程序很容易,一个 go 关键字就可以启动协程处理任务。Go 创建一个 goroutine 只需要 2K 内存空间,并且 go 协程上下文信息仅存储在两个寄存器中,对于 Go 运行时来说,切换上下文特别快。 不过凡事不加限制就会出问题,如果不加节制的滥用 goroutine 就可能导致内存泄露,增加 Go 运行时调度负担等。 下面看一段 Go http 标准库的代码: func (s *Server) Serve(l net.Listener) error { for { ... c := s.newConn(rw) go c.serve(connCtx) } } 当来一个 http 请求,http 会启动一个协程 Server.serve() 处理该请求。 这种方式对于请求的响应很快,但是如果有恶意大规模并发请求,就可能导致后端服务内存爆增,响应慢等问题。 测试千万并发的内存使用情况如下: const n = 10000000 func demoFunc() { time.Sleep(time.Duration(BenchParam) * time.Millisecond) } func TestNoPool(t *testing.T) { var start, end runtime.MemStats runtime.ReadMemStats(&start) var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go func() { demoFunc() wg.Done() }() } wg.Wait() runtime.ReadMemStats(&end) usageMem := end.TotalAlloc/MiB - start.TotalAlloc/MiB t.Logf("memory usage:%d MB", usageMem) } 测试结果: go test -v -bench=. -benchmem -run=TestNoPool === RUN TestNoPool ants_test.go:41: memory usage:1304 M --- PASS: TestNoPool (5.70s) 当并发量到千万时,内存使用率达到了 1 个多 G,看起来好像不多。不过可别忘了,这里处理的任务只是 time.Sleep,如果每个协程处理内存型或 IO 型任务,那该是多大内存或者 CPU 的消耗,并且调度如此多的协程也会让 Go 运行时(调度器,GC)的压力非常大。 因此,需要协程池来限制协程的使用,起到稳定内存使用率,减轻 Go 运行时负担的目的。 协程池 既然协程池的主要任务是限制协程的数量,那么作为池应该有几个需求是要实现的: 协程池生命周期管理,包括创建,释放等操作; 协程池的扩缩容,按需使用; 协程池中过期协程的清理; ants 是一个满足上述需求的高性能协程池。本文重点学习 ants 的源码了解协程池。
阅读全文