
go 程序中因主协程提前退出、通道未关闭导致日志无法写入文件,本文详解根本原因并提供基于 waitgroup 和安全文件操作的完整修复方案。
go 程序中因主协程提前退出、通道未关闭导致日志无法写入文件,本文详解根本原因并提供基于 waitgroup 和安全文件操作的完整修复方案。
上述代码看似逻辑清晰:主线程创建并立即关闭文件句柄,随后启动一个日志消费协程,通过 os.OpenFile 以 O_APPEND 模式反复打开文件写入;多个计算协程异步发送日志消息到通道。但运行后 log.txt 始终为空——问题并非出在文件权限或路径错误,而是两个关键的并发控制缺陷:
? 根本问题一:主协程过早退出
Go 程序的生命周期由 main 函数决定。当前代码中,main() 在启动所有 goroutine 后直接结束,操作系统随即终止整个进程,导致日志协程甚至来不及执行一次循环。即使通道中有待处理消息,也无机会被消费。
✅ 修复方式:使用 sync.WaitGroup 精确等待所有工作协程完成,并显式关闭日志通道以通知消费者退出。
? 根本问题二:通道未关闭,消费协程无限阻塞
for { msg, ok := 中的 <code>ok 仅在通道被 close() 且已无缓存数据时为 false。当前代码从未调用 close(logCh),因此 ok 永远为 true,协程陷入永久阻塞(或 panic,若通道被关闭后继续读取),无法进入 else { break } 分支。
立即学习“go语言免费学习笔记(深入)”;
✅ 修复方式:在所有生产者完成发送后,调用 close(logCh);消费者检测到 ok == false 即安全退出。
✅ 完整修复示例(含错误处理与最佳实践)
package main
import (
"fmt"
"os"
"sync"
"time"
)
func main() {
// 创建日志文件(确保初始存在,可选)
f, err := os.Create("./log.txt")
if err != nil {
panic("failed to create log file: " + err.Error())
}
f.Close() // 仅用于初始化,后续由消费者独占打开
logCh := make(chan string, 50)
var wg sync.WaitGroup
// 启动日志消费者协程
wg.Add(1)
go func() {
defer wg.Done()
for msg := range logCh { // 使用 range 自动检测关闭,更简洁安全
logTime := time.Now().Format(time.RFC3339)
f, err := os.OpenFile("./log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("WARN: failed to open log file: %v\n", err)
continue
}
if _, writeErr := f.WriteString(logTime + " - " + msg); writeErr != nil {
fmt.Printf("WARN: failed to write log: %v\n", writeErr)
}
f.Close() // 必须关闭,避免文件句柄泄漏
}
}()
// 启动生产者协程(共 81 个)
for i := 1; i < 10; i++ {
for j := 1; j < 10; j++ {
wg.Add(1)
go func(i, j int) {
defer wg.Done()
msg := fmt.Sprintf("%d + %d = %d\n", i, j, i+j)
logCh <- msg
fmt.Print(msg)
}(i, j)
}
}
// 所有生产者完成,关闭通道,通知消费者退出
go func() {
wg.Wait()
close(logCh)
}()
// 等待消费者协程结束
wg.Wait()
}⚠️ 关键注意事项
-
避免复用文件句柄:主线程中
f.Close()后不应再使用该变量;每个写入操作应独立OpenFile并及时Close,确保原子性与资源释放。 -
追加模式需组合标志:
os.O_APPEND必须与os.O_WRONLY或os.O_RDWR配合,单独使用os.ModeAppend(文件权限位)无效。 -
错误不可忽略:原代码中
_ := os.OpenFile(...)掩盖了磁盘满、权限不足等关键错误,生产环境务必检查err。 -
缓冲通道容量合理设置:
make(chan string, 50)可防生产者过快阻塞,但需根据实际吞吐量调整,避免内存积压。
通过以上修正,程序能可靠地将全部 81 条日志按时间顺序追加写入 log.txt,同时具备健壮的错误处理与资源管理能力。


















