
本文详解如何在 go 中通过 channel 协调多个 goroutine 向同一文件并发写入,重点解决因通道阻塞导致的死锁问题,并提供可运行、线程安全的完整示例。
本文详解如何在 go 中通过 channel 协调多个 goroutine 向同一文件并发写入,重点解决因通道阻塞导致的死锁问题,并提供可运行、线程安全的完整示例。
在 Go 中实现“一个写入器、多个生产者”的并发文件写入模式时,核心挑战在于通道的同步语义:无缓冲 channel(如 make(chan string))在发送操作 ch <- value 时会阻塞,直到有另一个 goroutine 执行接收操作 <-ch。原代码中,concurrent() 先尝试向 output 通道发送数据,但此时 printOutput() 尚未启动——导致所有发送 goroutine 永久阻塞,wg.Wait() 无法返回,close(output) 更无从执行,最终程序死锁。
✅ 正确做法:先启动消费者,再启动生产者
必须确保消费者 goroutine(即 printOutput())早于任何生产者启动,使其立即进入 for range output 循环并持续监听通道。这样,当 concurrent(1) 和 concurrent(2) 发送数据时,通道已有接收方,发送可立即完成:
func main() {
go printOutput() // ? 关键:消费者优先启动!
wg.Add(2)
go concurrent(1)
go concurrent(2)
wg.Wait()
close(output) // ? 所有生产者完成后再关闭通道
}? 补充说明与最佳实践
- range 语义:for msg := range output 会持续接收,直到通道被显式关闭(close(output))。若不关闭,循环永不退出;若过早关闭(如在 wg.Wait() 前),可能丢失未接收的数据。
- 文件句柄安全:printOutput() 中 f.Close() 使用 defer 是安全的,因为 for range 在通道关闭且所有已发送值被接收后自然退出,随后 defer 执行关闭。
-
错误处理建议:生产环境应检查 f.WriteString 的返回值(如磁盘满、权限不足),避免静默失败:
_, err := f.WriteString(msg + "\n") if err != nil { panic(fmt.Sprintf("failed to write '%s': %v", msg, err)) } - 性能优化(进阶):若写入频率极高,可考虑使用带缓冲通道(如 make(chan string, 100))或引入 bufio.Writer 减少系统调用开销。
✅ 最终可运行代码(修复版)
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() 在 goroutine 中不可靠,应直接调用
}
func printOutput() {
f, err := os.OpenFile("output.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer f.Close()
for msg := range output {
_, _ = f.WriteString(msg + "\n") // 忽略写入错误仅作演示;生产环境需处理
}
}
func main() {
go printOutput()
wg.Add(2)
go concurrent(1)
go concurrent(2)
wg.Wait()
close(output)
}? 提示:运行后检查 output.txt,将看到内容类似 1\n2\n(顺序不保证,符合并发预期)。该模式是构建日志系统、事件总线等场景的基础范式——始终让消费者先行,生产者后至,通道关闭收尾。

















