go读os.stdin不卡住的唯一可靠方式是用bufio.scanner并检查scanner.err();混用fmt.scan、bufio.reader或直接调os.stdin.read会导致卡死、丢行、错位。

Go 读 os.Stdin 不卡住的唯一可靠方式,是用 bufio.Scanner 并检查 scanner.Err();混用 fmt.Scan、bufio.Reader 或直接调 os.Stdin.Read,90% 的“卡死”“丢行”“错位”都源于此。
为什么 fmt.Scan 和 fmt.Scanln 容易卡住
它们不控制缓冲区边界,也不区分终端/管道输入,行为完全依赖上一次读取后残留的换行符或空格:
-
fmt.Scan会跳过开头所有空白(包括换行),但不会消费掉它——下一次读可能立刻返回空字符串 -
fmt.Scanln要求输入末尾必须是换行符,否则阻塞;且它不吞掉那个换行符,后续Scanner.Scan()第一次就可能返回空行 - 输入中混入全角空格、中文标点或多余回车,
fmt.Scanf("%s %d")就直接停住,连错误都不报
bufio.Scanner 的正确用法和三个关键配置点
它不是“更方便的 fmt.Scan”,而是一个带状态、需显式错误处理的流处理器:
- 必须在循环结束后调
scanner.Err()检查是否因超长行、I/O 错误等提前终止——漏判会导致静默丢数据 - 默认单行上限 64KB,超长触发
scanner.Err()==bufio.ErrTooLong;扩容写法:scanner.Buffer(make([]byte, 1024*1024), 1024*1024)(第二个参数是 max token size) - 不要在循环内重复创建
bufio.NewScanner(os.Stdin);goroutine 中也只应有一个 scanner 实例,否则缓冲区错位
什么时候不该用 Scanner?选 io.ReadAll 或 bufio.Reader
不是所有输入都是“一行一行的文本”,硬套 Scanner 反而增加复杂度:
- 读整个输入(如 base64、JSON blob):用
io.ReadAll(os.Stdin),它只在 EOF 或 error 时返回,适合管道场景 - 读单个 Unicode 字符(如 y/n 确认):用
bufio.NewReader(os.Stdin).ReadRune(),比ReadByte()正确支持中文 - 混合读取(先读 int 再读一整行):必须用
bufio.NewReader(os.Stdin),先ReadString('\n'),再strings.Fields()或strconv.Atoi()解析——Scanner和Reader不能共用同一个os.Stdin,否则底层缓冲区错位
Windows 下 Ctrl+Z 才是 EOF,不是 Ctrl+D
测试时别用错退出信号;另外,os.Stdin.Stat().Mode() & os.ModeCharDevice == 0 在容器、CI 或 Windows Git Bash 下经常误判,不可靠:
- 推荐用
github.com/mattn/go-isatty.IsTerminal(os.Stdin.Fd()),跨平台实测稳定 - 如果只是想“有输入就读,没输入就跳过”,别依赖设备类型——改用带超时的非阻塞读:
time.AfterFunc(100*time.Millisecond, func(){})包一层os.Stdin.Read,或直接起 goroutine +scanner.Scan()
最常被忽略的点:scanner.Text() 在 scanner.Scan() 返回 false 后仍返回上一次成功值,而不是当前状态;真正要判断读取是否完成,只能靠 scanner.Err() 和循环退出逻辑。这导致大量程序在遇到超长行或管道中断时静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











