You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言中扩展无限循环中断方式:新增终端Q键终止功能问询

实现Go程序的双方式终止:Ctrl+C + Q键退出

嘿,这个需求完全可以搞定!咱们可以同时监听**SIGINT信号(Ctrl+C)**和终端的键盘输入,让程序既能保留原来的终止方式,又能支持按Q(或小写q)直接退出循环。下面给你具体的实现思路和代码示例:

核心思路

我们需要用两个goroutine分别处理两种终止触发源:

  1. 一个goroutine监听系统SIGINT信号,保留你原来的Ctrl+C终止逻辑;
  2. 另一个goroutine监听终端键盘输入,捕获Q/q按键并触发退出;
  3. 用一个退出通道(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:08:44