
go 程序中,若在 main 函数启动 goroutine 后立即返回,程序会直接退出,导致该 goroutine 未执行完即被强制终止——这是通道写入失效(如仅发送一个值却无响应)的根本原因。
go 程序中,若在 main 函数启动 goroutine 后立即返回,程序会直接退出,导致该 goroutine 未执行完即被强制终止——这是通道写入失效(如仅发送一个值却无响应)的根本原因。
在您提供的代码中,WriteDeviceToFile 是在 goroutine 中异步调用的,但 main 函数在发送两个设备后调用 close(deviceChan),随即结束。Go 运行时不会等待未完成的 goroutine —— 一旦 main 函数返回,整个程序立即终止,无论后台 goroutine 是否仍在读取通道、序列化数据或写入文件。
这解释了为何“只发一个设备时函数不接收任何值”:goroutine 可能尚未开始执行 for device := range d 循环,main 就已退出;而发送两个设备时,因调度偶然性(如 goroutine 被更快唤醒),看似“成功”,实则属于未定义行为(race condition),不可靠。
✅ 正确做法是确保 main 等待 goroutine 完成。推荐使用 sync.WaitGroup:
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
)
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_CREATE|os.O_WRONLY, 0600)
if err != nil {
panic(fmt.Sprintf("failed to open file: %v", err))
}
defer f.Close() // 注意:此处 defer 在函数返回时触发,安全
for device := range d {
deviceB, err := json.Marshal(device)
if err != nil {
panic(fmt.Sprintf("JSON marshal error: %v", err))
}
fmt.Println(string(deviceB))
if _, err = f.Write(deviceB); err != nil { // 推荐 Write 而非 WriteString,避免 UTF-8 编码歧义
panic(fmt.Sprintf("file write error: %v", err))
}
if _, err = f.WriteString("\n"); err != nil {
panic(fmt.Sprintf("write newline error: %v", err))
}
}
}
func main() {
var wg sync.WaitGroup
deviceChan := make(chan *models.Device, 1) // 可选:加缓冲避免 sender 阻塞
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() // ✅ 关键:阻塞 main,等待 goroutine 完全处理完毕
}⚠️ 注意事项:
-
defer f.Close()在WriteDeviceToFile中是安全的,因f在函数作用域内有效;但必须确保f成功打开(需检查err)。 - 原代码中
os.OpenFile忽略了错误(f, _ = ...),可能导致 panic 发生在f.WriteString上且无明确上下文,应显式校验。 - 使用
f.Write([]byte)比f.WriteString(string)更高效且语义清晰(json.Marshal返回[]byte)。 - 通道缓冲(如
make(chan *models.Device, 1))可缓解 sender 端阻塞,但不能替代同步机制。 -
os.O_CREATE应加入 flag,避免文件不存在时报错。
总结:Go 的并发模型要求显式协调生命周期。main 不等待 ≠ goroutine 自动等待;务必通过 WaitGroup、channel 信号或 sync.Once 等机制确保关键任务完成,否则程序行为不可预测。

















