
go 程序中,若在 main 函数结束前未等待 goroutine 完成,会导致其被强制终止,通道接收逻辑(如写文件)可能完全失效,尤其在仅发送单个值时表现明显。
go 程序中,若在 main 函数结束前未等待 goroutine 完成,会导致其被强制终止,通道接收逻辑(如写文件)可能完全失效,尤其在仅发送单个值时表现明显。
在您提供的代码中,WriteDeviceToFile 在 goroutine 中异步执行,但 main 函数在向 deviceChan 发送两个设备、关闭通道后立即返回——此时程序进程直接退出,不会等待 goroutine 执行完毕。即使通道已关闭、goroutine 已开始运行,只要 main 返回,整个程序就会终止,导致 f.WriteString 可能根本来不及执行(尤其当仅发送一个设备时,goroutine 甚至可能尚未调度到 for device := range d 循环)。
这是 Go 并发编程中最常见的陷阱之一:goroutine 的生命周期不自动绑定于主函数。go 启动的协程是“火即走”(fire-and-forget)式的,若无显式同步机制,无法保证其完成。
✅ 正确做法:使用 sync.WaitGroup 或 channel 实现主协程等待:
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"sync" // 新增导入
"/something/models"
)
func WriteDeviceToFile(d chan *models.Device, fileName string, wg *sync.WaitGroup) {
defer wg.Done() // 标记 goroutine 完成
_, b, _, _ := runtime.Caller(0)
basepath := filepath.Dir(b)
filePath := basepath + "/dataFile/" + fileName
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
panic(err)
}
defer f.Close() // 注意:defer 在函数返回时执行,此处安全
for device := range d {
deviceB, err := json.Marshal(device)
if err != nil {
panic(err)
}
fmt.Println(string(deviceB))
if _, err = f.WriteString(string(deviceB) + "\n"); err != nil {
panic(err)
}
}
}
func main() {
var wg sync.WaitGroup
deviceChan := make(chan *models.Device)
wg.Add(1)
go WriteDeviceToFile(deviceChan, "notalive.txt", &wg)
d := models.NewDevice("12346", "")
deviceChan <- d
d = models.NewDevice("abcd", "")
deviceChan <- d
close(deviceChan)
wg.Wait() // 主函数阻塞,直到 goroutine 完成
}⚠️ 关键修正说明:
-
wg.Add(1)+wg.Done()+wg.Wait()构成标准等待模式,确保main不提前退出; -
defer f.Close()移至OpenFile成功后,避免对nil文件句柄调用Close; -
f.WriteString后追加\n提升 JSON 文件可读性(每条记录一行); - 移除了
f, _ = os.OpenFile(...)中的错误忽略——必须检查err,否则静默失败; -
panic(err)仅用于演示;生产环境建议用日志+优雅退出。
? 补充提示:
- 若需更高可靠性(如确保文件落盘),可在
f.Close()前调用f.Sync(); - 对于大量设备写入,考虑批量 marshaling 或使用
json.Encoder流式写入,提升性能与内存效率; - 避免在 goroutine 中 panic —— 应通过 channel 或 error 返回值传递错误,由主协程统一处理。
总之,Go 的并发不是“自动托管”,显式同步是健壮性的基石。永远记住:main 结束 = 程序死亡,与后台 goroutine 是否完成无关。

















