为何Go语言程序中readStdIn函数始终无法执行完成?
背景与需求
原本通过curl获取端点数据并管道传入Go程序,初始读取逻辑运行正常:
reader := bufio.NewReader(os.Stdin) var buf bytes.Buffer line, err := reader.ReadString(`\n`) for { if err != nil{ buf.WriteString(line) break } buf.WriteString(line) } var data Memstats err = json.Unmarshal(buf.Bytes(), &data)
最终需求是通过以下循环命令,让程序按间隔读取10个JSON数据:
for i in {1..10}; do curl localhost:6000/debug/vars | ./myprogram; sleep 1; done
将读取逻辑移到readStdIn函数后,函数始终无法执行完成,请求排查原因。
主函数代码
for{ reader := bufio.NewReader(os.Stdin) h.readStdIn(reader) time.Sleep(1 * time.Second) var err error //检查curl是否还会发送更多数据,若无则跳出循环 newbytes, err := reader.ReadByte() if err != nil{ break } }
readStdIn函数代码
func (rt *Graph)readStdIn(reader *bufio.Reader){ var buf bytes.Buffer line, err := reader.ReadString('\n') for { if err != nil{ if err == io.EOF{ buf.WriteString(line) break }else{ fmt.Println(err.Error()) os.Exit(1) } } } buf.WriteString(line) } var data Memstats err = json.Marshal(buf.Bytes(), &data) // 此处为笔误,应为Unmarshal rt.Memstats = append(rt.Memstats, &record) }
问题核心原因
readStdIn函数循环逻辑错误
函数内的for循环没有持续读取新行:第一次调用ReadString后,若没有错误,循环会无限空转——因为循环体内没有再次执行line, err := reader.ReadString('\n'),err始终为nil,永远无法触发退出条件。对管道执行逻辑的误解
你写的循环命令中,每次curl | ./myprogram都会启动一个全新的./myprogram进程,而非让同一个进程接收10次curl数据。也就是说,Go程序每次只需要处理单次curl的输入,处理完就可以退出,不需要在进程内循环读取。主函数循环冗余且错误
主函数的for循环完全没必要:单次进程的标准输入只有一份curl数据,读取完就会收到EOF,不需要循环等待。每次循环创建新的bufio.Reader,还尝试ReadByte检查后续数据,只会导致进程阻塞。代码低级笔误
json.Marshal应为json.Unmarshal,否则无法将JSON字节反序列化为结构体;&record未定义,实际应为&data。
修复方案
方案1:适配现有命令逻辑(单次进程处理单次输入)
修改Go程序,去掉主函数的循环,只处理一次标准输入即可:
简化后的主函数:
func main() { h := &Graph{} reader := bufio.NewReader(os.Stdin) h.readStdIn(reader) // 可在此处添加数据输出/存储逻辑 }
修复后的readStdIn函数:
func (rt *Graph) readStdIn(reader *bufio.Reader) { var buf bytes.Buffer for { line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { buf.WriteString(line) break } fmt.Println(err.Error()) os.Exit(1) } buf.WriteString(line) } var data Memstats err := json.Unmarshal(buf.Bytes(), &data) if err != nil { fmt.Println("反序列化失败:", err) os.Exit(1) } rt.Memstats = append(rt.Memstats, &data) }
此时你的循环命令会正常执行10次,每次启动新的程序实例处理单次curl输入。
方案2:单个进程接收多次输入(修改命令逻辑)
若希望单个Go进程接收10次curl数据,需修改命令让所有curl输出管道到同一个进程:
(for i in {1..10}; do curl localhost:6000/debug/vars; sleep 1; done) | ./myprogram
此时Go程序需循环读取完整JSON,推荐用json.Decoder直接读取,无需手动拼接缓冲区:
func main() { h := &Graph{} reader := bufio.NewReader(os.Stdin) dec := json.NewDecoder(reader) for { var data Memstats err := dec.Decode(&data) if err == io.EOF { break } if err != nil { fmt.Println("反序列化失败:", err) os.Exit(1) } rt.Memstats = append(rt.Memstats, &data) } }
json.Decoder可自动处理JSON的边界,即使JSON跨多行也能正确读取。
内容的提问来源于stack exchange,提问作者Leahcim

