答案:高并发下Go日志写入需避免数据竞争,可通过Mutex加锁或channel异步队列实现线程安全;前者简单但性能低,后者解耦生产消费、支持缓冲,结合lumberjack还可实现轮转,推荐高并发场景使用。

在高并发场景下,多个协程同时写入日志容易引发数据竞争、文件损坏或日志错乱。Golang 虽然没有内置的并发安全日志系统,但通过合理设计可以轻松构建一个高效、线程安全的日志模块。以下是实战中常用的多协程文件写入方案。
使用互斥锁(Mutex)保护文件写入
最直接的方式是用 sync.Mutex 保证同一时间只有一个协程能执行写操作。
示例代码:package mainimport ( "log" "os" "sync" )
var ( file os.File mu sync.Mutex logger log.Logger )
func init() { var err error file, err = os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Fatal("无法打开日志文件:", err) } logger = log.New(file, "", log.LstdFlags) }
func Log(message string) { mu.Lock() defer mu.Unlock() logger.Println(message) }
func main() { defer file.Close()
var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(id int) { defer wg.Done() Log("来自协程 " + string(rune(id+'0'))) }(i) } wg.Wait()}
优点:实现简单,逻辑清晰。缺点:高并发下锁竞争严重,性能下降。
使用 channel 实现日志队列异步写入
更高效的方案是引入消息队列模型,所有协程将日志发送到 channel,由单个写入协程处理落盘。
立即学习“go语言免费学习笔记(深入)”;
改进版示例:package mainimport ( "bufio" "log" "os" )
type Logger struct { ch chan string }
func NewLogger(filename string, bufferSize int) *Logger { l := &Logger{ ch: make(chan string, bufferSize), } file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Fatal("打开日志文件失败:", err) }
writer := bufio.NewWriter(file) go func() { for line := range l.ch { _, _ = writer.WriteString(line + "\n") _ = writer.Flush() // 可根据性能需求调整是否实时 flush } _ = writer.Flush() _ = file.Close() }() return l}
func (l *Logger) Log(msg string) { select { case l.ch
func main() { logger := NewLogger("async.log", 1000)
var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func(id int) { defer wg.Done() logger.Log("处理完成 - ID:" + strconv.Itoa(id)) }(i) } wg.Wait() // 简单等待,实际中可用 context 或 close channel 控制退出 time.Sleep(time.Second)}
优势:写入协程串行化,避免锁开销;生产消费解耦;支持缓冲和背压控制。
结合轮转(Rotate)与并发安全设计
真实项目中还需考虑日志文件大小限制和轮转。可封装第三方库如 lumberjack 配合上述模式使用。
集成 lumberjack 示例:import ( "gopkg.in/natefinch/lumberjack.v2" "log" )func NewRotatingLogger() *log.Logger { writer := &lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 10, // MB MaxBackups: 5, MaxAge: 7, // 天 Compress: true, } return log.New(writer, "", log.LstdFlags) }
lumberjack 本身是线程安全的,可直接用于多协程环境。若配合 channel 模式,性能更优且不影响主流程响应速度。
基本上就这些。选择哪种方案取决于性能要求和系统复杂度。小项目用 Mutex 足够,高并发服务推荐 channel + 异步写入模型。关键是避免多个 goroutine 直接操作同一个文件句柄。










