cmd.run()会阻塞,因为它同步执行start()+wait()并等待子进程退出才返回;需超时控制、实时读日志或并发管理时,必须改用start()+wait()组合,并配合context.withtimeout或goroutine处理i/o。

启动子进程时为什么 cmd.Run() 会阻塞?
因为 cmd.Run() 是同步调用,它会一直等到子进程退出才返回,期间 Go 主协程被挂起。如果你需要并发控制、超时或实时读取输出,就不能用它。
正确做法是用 cmd.Start() 启动后立即返回,再配合 cmd.Wait() 或 cmd.Process.Wait() 等待结束——但注意,Wait() 仍是阻塞的,只是你有了调度自由度。
- 想带超时?用
context.WithTimeout()包裹cmd.Wait() - 想边跑边读 stdout?提前设置
cmd.Stdout = &bytes.Buffer{}或用io.Pipe() - 忘记调用
Start()就直接Wait()?会 panic:exec: not started
如何安全终止正在运行的子进程(包括其子进程树)?
Go 默认只杀直接子进程,不会递归终止它的子进程(比如 sh -c "sleep 10 &" 启动的后台任务)。Linux 下靠 syscall.SIGKILL 或 syscall.SIGTERM 发给进程组更可靠。
关键点:启动前用 syscall.SysProcAttr.Setpgid = true 让子进程自成进程组,之后向整个组发信号。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
- Windows 不支持进程组,得用
cmd.Process.Kill()+ 手动遍历子进程(需 WMI 或第三方库) - 别用
cmd.Process.Signal(syscall.SIGKILL)直接杀单个进程——它可能留下孤儿子进程 - 调用
cmd.Process.Kill()前务必确认cmd.Process != nil,否则 panic:invalid memory address
cmd.Output() 和 cmd.CombinedOutput() 的适用边界在哪?
这两个是便捷封装,适合「执行完就结束、输出量小、不需要流式处理」的场景。它们内部调用 Run(),所以本质仍是同步阻塞。
区别只在 stderr 处理:Output() 把 stderr 丢弃(重定向到 os.DevNull),CombinedOutput() 把 stdout 和 stderr 合并输出。
- 命令预期输出 > 1MB?小心内存爆掉——
Output()全部加载进内存 - 需要区分 stdout/stderr 错误来源?必须手动设
cmd.Stdout和cmd.Stderr为不同io.Writer - 命令本身不输出但 exit code 非 0?
Output()仍会返回 error,别忽略err != nil判断
为什么子进程的标准输入/输出管道容易死锁?
典型现象:调用 cmd.Stdin.Write() 后卡住,或者 cmd.Stdout.Read() 一直等不到 EOF。根本原因是 Go 的 os/exec 默认使用带缓冲的管道,而子进程和 Go 程序之间没有协调好读写节奏。
常见诱因是「全写完再读」或「全读完再写」——比如先 stdin.Write() 写满缓冲区,子进程还没开始读,Go 就卡住了;或者子进程输出太多,Go 没及时读,管道满导致子进程阻塞。
- 解决办法:用 goroutine 并发读写,例如
go io.Copy(cmd.Stdout, outReader) - 写完必须调用
cmd.Stdin.Close(),否则子进程可能永远等不到 EOF - 避免用
bytes.Buffer.String()直接转大输出——改用bytes.Buffer.Bytes()防止 UTF-8 解码失败
Close() 或少设一个 Setpgid,就可能让进程变成僵尸。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










