Go语言中扩展无限循环中断方式:新增终端Q键终止功能问询
实现Go程序的双方式终止:Ctrl+C + Q键退出
嘿,这个需求完全可以搞定!咱们可以同时监听**SIGINT信号(Ctrl+C)**和终端的键盘输入,让程序既能保留原来的终止方式,又能支持按Q(或小写q)直接退出循环。下面给你具体的实现思路和代码示例:
核心思路
我们需要用两个goroutine分别处理两种终止触发源:
- 一个goroutine监听系统SIGINT信号,保留你原来的Ctrl+C终止逻辑;
- 另一个goroutine监听终端键盘输入,捕获Q/q按键并触发退出;
- 用一个退出通道(
quit chan struct{})来统一通知主循环终止,确保两种方式都能安全停止程序。
完整代码实现(含第三方库简化版)
这里推荐使用github.com/pkg/term库来处理终端模式,它能帮我们快速切换终端的原始模式(让按键即时响应,不需要回车),避免手动调用复杂的系统调用。
首先安装依赖:
go get github.com/pkg/term
然后编写主程序(比如main.go):
package main import ( "fmt" "os" "os/signal" "syscall" "time" "github.com/pkg/term" ) func main() { // 创建退出通知通道 quit := make(chan struct{}) // 1. 监听SIGINT信号(Ctrl+C) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT) go func() { <-sigChan fmt.Println("\n收到Ctrl+C信号,准备退出...") close(quit) }() // 2. 监听键盘输入:按Q/q退出 go func() { // 打开终端并保存原始设置,退出时恢复 tty, err := term.Open("/dev/tty") if err != nil { fmt.Printf("终端初始化失败: %v\n", err) return } defer tty.Restore() defer tty.Close() // 设置终端为原始模式:按键即时响应,不回显、不等待回车 term.RawMode(tty) for { buf := make([]byte, 1) _, err := tty.Read(buf) if err != nil { fmt.Printf("读取键盘输入失败: %v\n", err) return } key := string(buf) if key == "Q" || key == "q" { fmt.Println("\n按下Q键,准备退出...") close(quit) return } } }() // 3. 无限计数循环 fmt.Println("程序启动成功!按Q/q或Ctrl+C即可退出") count := 0 for { select { case <-quit: fmt.Println("\n循环终止,程序已退出") return default: count++ fmt.Printf("\r当前计数: %d", count) // \r实现行内刷新 time.Sleep(500 * time.Millisecond) } } }
无第三方库的原生实现
如果你不想引入外部依赖,也可以用标准库的syscall手动控制终端模式,替换上面的键盘监听goroutine即可:
// 替换原来的键盘监听goroutine go func() { fd := int(os.Stdin.Fd()) // 保存终端原始属性,退出时必须恢复 oldState, err := syscall.Tcgetattr(fd) if err != nil { fmt.Printf("获取终端属性失败: %v\n", err) return } defer syscall.Tcsetattr(fd, syscall.TCSANOW, oldState) // 设置终端为原始模式 newState := oldState newState.Lflag &^= syscall.ICANON | syscall.ECHO // 关闭行缓冲和输入回显 newState.Cc[syscall.VMIN] = 1 // 至少读取1个字节 newState.Cc[syscall.VTIME] = 0 // 不设置超时 if err := syscall.Tcsetattr(fd, syscall.TCSANOW, &newState); err != nil { fmt.Printf("设置终端模式失败: %v\n", err) return } for { buf := make([]byte, 1) _, err := os.Stdin.Read(buf) if err != nil { fmt.Printf("读取键盘输入失败: %v\n", err) return } key := string(buf) if key == "Q" || key == "q" { fmt.Println("\n按下Q键,准备退出...") close(quit) return } } }()
关键注意点
- 终端恢复:无论用哪种方式,一定要在退出时恢复终端的原始设置!否则终端会出现输入不回显、无法正常换行等异常。
- 行内刷新:用
\r实现计数的行内刷新,避免终端输出大量重复行。 - 通道安全:通过关闭
quit通道来通知主循环,这种方式能保证所有触发源都能安全触发退出,不会出现竞态问题。
内容的提问来源于stack exchange,提问作者Kstulen
相关产品推荐
相关产品推荐

