
本文详解如何通过goroutine与channel安全、高效地并发写入文件,重点解决因通道阻塞导致的死锁问题,并提供可运行的完整示例与关键注意事项。
本文详解如何通过goroutine与channel安全、高效地并发写入文件,重点解决因通道阻塞导致的死锁问题,并提供可运行的完整示例与关键注意事项。
在Go中利用channel协调并发写文件时,一个常见误区是未及时消费通道数据,从而引发goroutine永久阻塞——这正是原代码无法写入文件的根本原因。output <- fmt.Sprint(n) 是一个同步发送操作:若无接收方就绪,该语句将一直等待,导致 concurrent goroutine卡住,wg.Wait() 永不返回,close(output) 也无法执行,最终 printOutput() 中的 for range output 根本不会开始迭代(因为通道始终未被关闭,且无任何值被成功发送)。
正确的做法是让消费者(printOutput)先于生产者启动并持续监听通道,确保每次发送都能被即时接收。修改后的代码将 go printOutput() 提前至 main 函数开头,使其作为后台写入服务长期运行:
package main
import (
"fmt"
"os"
"sync"
)
var wg sync.WaitGroup
var output = make(chan string)
func concurrent(n uint64) {
output <- fmt.Sprint(n)
wg.Done() // 注意:defer wg.Done() 在此处无效(函数已返回),应直接调用
}
func printOutput() {
f, err := os.OpenFile("output.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer f.Close()
for msg := range output {
if _, err := f.WriteString(msg + "\n"); err != nil {
panic(err) // 生产环境建议记录错误而非panic
}
}
}
func main() {
go printOutput() // ✅ 关键:消费者goroutine必须先启动
wg.Add(2)
go concurrent(1)
go concurrent(2)
wg.Wait() // 等待所有生产者完成
close(output) // ✅ 关闭通道,通知消费者退出循环
}关键改进与注意事项:
- 启动顺序决定成败:printOutput 必须作为goroutine提前运行,否则所有 output <- ... 将阻塞;
- 显式关闭通道:close(output) 是 for range 正常退出的唯一信号,必须在所有发送完成后调用;
- 错误处理增强:f.WriteString 可能失败,应检查返回的 err;
- 文件打开模式优化:os.O_RDWR 改为 os.O_WRONLY 更符合只写场景,权限 0644 比 0666 更安全;
- defer wg.Done() 的陷阱:原代码中 defer 在 concurrent 函数返回后才执行,但此时goroutine可能已退出,wg.Done() 实际未生效——应直接调用 wg.Done()(如示例所示)。
此模式实现了“生产者-消费者”解耦:多个goroutine专注生成数据,单一goroutine串行写入文件,既避免了竞态条件,又保证了I/O效率与数据一致性。
立即学习“go语言免费学习笔记(深入)”;


















