如何复用goroutine,而非为每个异步调用创建新goroutine
使用Goroutine池优化日志函数的异步执行
完全可以用Channel实现固定大小的Goroutine池,同时满足你提出的两个约束。核心思路是用带缓冲的Channel作为任务队列,维持固定数量的Worker Goroutine复用执行任务,当队列满时直接新开Goroutine处理任务。
实现方案
- 定义任务类型:将日志序列化+写入的逻辑封装为可执行的任务函数。
- 初始化固定Worker池:启动指定数量(比如10个)的Worker,持续从任务队列中获取任务执行。
- 非阻塞提交任务:调用
Log函数时,尝试将任务发送到队列;若队列已满,则直接在新Goroutine中执行任务。
完整代码示例
package main import ( "encoding/json" "os" "sync" ) // LogTask 封装日志执行逻辑的任务类型 type LogTask func() var ( taskQueue = make(chan LogTask, 10) // 缓冲大小=Worker数量,控制池内并发数 logFileMutex sync.Mutex // 文件写入互斥锁,避免并发写入混乱 ) // 初始化Goroutine池:启动固定数量的Worker func init() { for i := 0; i < 10; i++ { go func() { // Worker持续监听任务队列,有任务就执行 for task := range taskQueue { task() } }() } } func Log(v interface{}) { // 封装日志处理逻辑为任务 task := func() { // 1. 序列化对象为JSON buf, err := json.Marshal(v) if err != nil { // 可根据需求添加错误处理,比如输出到标准错误 return } // 2. 安全写入文件(加锁避免并发冲突) logFileMutex.Lock() defer logFileMutex.Unlock() f, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return } defer f.Close() _, _ = f.Write(buf) _, _ = f.WriteString("\n") } // 非阻塞提交任务:队列未满则入队,满则直接开新Goroutine执行 select { case taskQueue <- task: // 任务成功加入队列,由池内Worker执行 default: // 队列已满,直接异步执行任务 go task() } }
关键细节说明
- Worker复用:
init函数启动的10个Worker会一直存活,循环处理队列中的任务,避免频繁创建销毁Goroutine的开销。 - 约束满足:
- 每次调用
Log仅向队列提交一个任务,符合“每次仅添加一个任务”的要求; - 通过
select的default分支处理队列满的情况,直接新开Goroutine执行任务,不会扩容池内Worker数量。
- 每次调用
- 并发安全:写入文件时加了
sync.Mutex,避免多个Worker同时写入导致日志内容错乱。
内容的提问来源于stack exchange,提问作者Alexander Mills
相关产品推荐
相关产品推荐

