在 Go 中前台处理信号

GGG*_*GGG 1 signals foreground go

我想在后台监听操作系统信号,但在前台处理它。

这是示例代码。主循环无限期地每秒打印“boop”。但是,当收到中断信号时,处理程序会打印“Terminate Slow...”,并等待五秒钟,然后再终止程序。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)
    go func() {
        <-c
        fmt.Println("Terminating slowly...")
        time.Sleep(time.Duration(5)*time.Second)
        os.Exit(0)
    }()

    for {
        fmt.Println("boop")
        time.Sleep(time.Duration(1)*time.Second)
    }
}
Run Code Online (Sandbox Code Playgroud)

当信号被处理时,我希望其他一切都被阻止。但目前在缓慢终止的五秒内,它不断打印“boop”。

我明白了:

boop
boop
^CTerminating slowly...
boop
boop
boop
boop
boop
Run Code Online (Sandbox Code Playgroud)

我想要这个:

boop
boop
^CTerminating slowly...
Run Code Online (Sandbox Code Playgroud)

真正的程序是一个基于堆栈的语言解释器,用户可以在终止时运行某些内容,但目前主循环可以同时更改堆栈。

jub*_*0bs 5

正如mh-cbon 指出的那样select您在这里需要一个声明。一旦将循环体包装在 a 的默认情况下select,您就不再需要启动另一个 goroutine;使用select案例来完成这项工作。

另外,您使用的是无缓冲通道,但根据Notify的文档,该函数需要适当的缓冲通道:

包信号不会阻塞发送到c:调用者必须确保c有足够的缓冲空间来跟上预期的信号速率。对于仅用于通知一个信号值的通道,大小为 1 的缓冲区就足够了。

package main

import (
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {
    c := make(chan os.Signal, 1) // buffered
    signal.Notify(c, os.Interrupt)
    for {
        select {
        case <-c:
            fmt.Println("Terminating slowly...")
            time.Sleep(time.Duration(5) * time.Second)
            return
        default:
            fmt.Println("boop")
            time.Sleep(time.Duration(1) * time.Second)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦信号发送到通道c,非默认情况就变成非阻塞并被selected,并且函数结束。

以下是该程序的一种可能输出(在该特定执行过程中,我^C在大约 4 秒后点击):

boop
boop
boop
boop
^CTerminating slowly...
Run Code Online (Sandbox Code Playgroud)