如何消除Go协程的初始启动开销?
如何消除Go协程的初始启动开销?
首先得帮你理清问题根源:你看到的第一次协程启动的50ms左右延迟,不是Go协程本身的创建开销(Go协程本身非常轻量,创建成本极低),而是Go的调度器在第一次启动协程时,需要完成一些内部初始化工作——比如创建第一个系统线程(M)、初始化处理器(P)的调度队列等,这些操作会带来一次性的额外开销。你之前提前启动那个无用的action(-1)协程,本质上是提前触发了调度器的初始化,所以后续真正需要的协程启动就快了。
下面给你几个更优雅的解决方案,不用再启动无用的业务协程:
方案一:轻量预热调度器
只需要在程序启动时,启动一个空的协程来触发调度器初始化,比原来的无用action要轻量得多:
func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) var wg sync.WaitGroup // 预热调度器:启动一个空协程,触发初始化 go func() {}() // 可选:加个极短的休眠,确保调度器完成初始化(大部分场景下不加也能生效) time.Sleep(10 * time.Millisecond) for i := 0; i < 10; i++ { time.Sleep(100 * time.Millisecond) msg := msg(i) log.Println(msg, "th message received") wg.Add(1) go action(msg, &wg) } wg.Wait() }
这个空协程不会做任何实际工作,只是让Go runtime提前完成调度器的初始化流程,后续你的业务协程启动时就不会有初始延迟了。
方案二:使用协程池(适合高并发场景)
如果你的业务后续会有大量的消息需要处理,提前创建一批协程组成“协程池”来等待任务,不仅能消除初始调度开销,还能更好地控制并发数:
func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) var wg sync.WaitGroup // 创建任务通道,协程池的协程会从这里获取任务 taskChan := make(chan int, 10) // 定义协程池大小,根据你的业务并发需求调整 poolSize := 5 // 启动协程池 for p := 0; p < poolSize; p++ { go func() { // 持续监听任务通道,直到通道关闭 for task := range taskChan { action(task, &wg) } }() } // 模拟接收消息并发送到协程池 for i := 0; i < 10; i++ { time.Sleep(100 * time.Millisecond) msg := msg(i) log.Println(msg, "th message received") wg.Add(1) taskChan <- msg } // 关闭任务通道,让协程池的协程能正常退出 close(taskChan) wg.Wait() }
这种方式下,协程池的协程在程序启动时就已经初始化完成,后续收到消息直接把任务发送到通道即可,完全不会有初始启动延迟,同时还能避免频繁创建销毁协程的开销(虽然Go协程开销小,但高并发下还是有意义)。
额外说明
Go的调度器初始化是一次性的,只要程序中启动过至少一个协程,后续的协程启动就不会再有这个初始延迟。所以不管用哪种方案,核心都是提前触发调度器的初始化,避免在第一次处理业务请求时才执行这个操作。
希望这些方案能解决你的问题,根据你的实际业务场景选择合适的方式就好~
内容来源于stack exchange
相关产品推荐
相关产品推荐

