You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复用goroutine,而非为每个异步调用创建新goroutine

使用Goroutine池优化日志函数的异步执行

完全可以用Channel实现固定大小的Goroutine池,同时满足你提出的两个约束。核心思路是用带缓冲的Channel作为任务队列,维持固定数量的Worker Goroutine复用执行任务,当队列满时直接新开Goroutine处理任务。

实现方案

  1. 定义任务类型:将日志序列化+写入的逻辑封装为可执行的任务函数。
  2. 初始化固定Worker池:启动指定数量(比如10个)的Worker,持续从任务队列中获取任务执行。
  3. 非阻塞提交任务:调用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的开销。
  • 约束满足:
    1. 每次调用Log仅向队列提交一个任务,符合“每次仅添加一个任务”的要求;
    2. 通过select的default分支处理队列满的情况,直接新开Goroutine执行任务,不会扩容池内Worker数量。
  • 并发安全:写入文件时加了sync.Mutex,避免多个Worker同时写入导致日志内容错乱。

内容的提问来源于stack exchange,提问作者Alexander Mills

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 03:47:48