Handle a signal in the foreground in Go

huangapple go评论92阅读模式
英文:

Handle a signal in the foreground in Go

问题

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

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

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)
	}
}

当处理信号时,我希望其他所有操作都被阻塞。
但是当前在慢速终止的五秒钟期间,它仍然会打印“boop”。

我得到的输出是:

boop
boop
^CTerminating slowly...
boop
boop
boop
boop
boop

我希望的输出是:

boop
boop
^CTerminating slowly...

实际的程序是基于堆栈的语言解释器,用户可以在终止时运行某些操作,但当前主循环可以同时更改堆栈。

英文:

I would like to listen for a OS signal in the background but handle it in the foreground.

Here's example code.
The mainloop prints "boop" every second indefinitely.
But when an interrupt signal is received, the handler prints "Terminating slowly..." and waits five seconds before terminating the program.

package main

import (
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;time&quot;
)

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

	for {
		fmt.Println(&quot;boop&quot;)
		time.Sleep(time.Duration(1)*time.Second)
	}
}

When the signal is being handled, I want everything else to block.
But currently during the five seconds of slow termination, it keeps printing "boop".

I get this:

boop
boop
^CTerminating slowly...
boop
boop
boop
boop
boop

I'd like this:

boop
boop
^CTerminating slowly...

The real program is a stack-based-language interpreter where the user can have something run on termination, but currently the mainloop can change the stack at the same time.

答案1

得分: 5

mh-cbon指出,在这里你需要使用select语句。一旦你将循环体包裹在select的默认情况下,你就不再需要启动另一个goroutine;可以使用select的一个case来完成这项工作。

另外,你正在使用一个无缓冲的channel,但是根据Notify的文档,该函数需要一个适当缓冲的channel:

> signal包不会阻塞发送到c的操作:调用者必须确保c具有足够的缓冲空间以跟上预期的信号速率。对于仅用于通知一个信号值的channel,大小为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)
		}
	}
}

一旦信号被发送到channel c,非默认的case就变为非阻塞状态,并被select选中,函数就会结束。

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

boop
boop
boop
boop
^CTerminating slowly...
英文:

As mh-cbon points out, a select statement is what you need, here. Once you wrap the body of your loop inside the default case of a select, you no longer need to start another goroutine; use a select case to do that work.

Also, you're using an unbuffered channel, but according to Notify's documentation, that function expects an appropriately buffered channel:

> Package signal will not block sending to c: the caller must ensure that c has sufficient buffer space to keep up with the expected signal rate. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient.

package main

import (
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;time&quot;
)

func main() {
	c := make(chan os.Signal, 1) // buffered
	signal.Notify(c, os.Interrupt)
	for {
		select {
		case &lt;-c:
			fmt.Println(&quot;Terminating slowly...&quot;)
			time.Sleep(time.Duration(5) * time.Second)
			return
		default:
			fmt.Println(&quot;boop&quot;)
			time.Sleep(time.Duration(1) * time.Second)
		}
	}
}

As soon as a signal is sent to channel c, the non-default case becomes non-blocking and gets selected, and the function ends.

Here is one possible output of the program (during that specific execution, I hit ^C after about 4 seconds):

boop
boop
boop
boop
^CTerminating slowly...

答案2

得分: 0

虽然“select”答案肯定是最好的,但我想补充一下,如果你将逻辑反转,结果可能会还可以:

package main

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

func main() {
	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt)
	go func() {
		for {
			fmt.Println("boop")
			time.Sleep(time.Duration(1) * time.Second)
		}
	}()

	<-c
	fmt.Println("Terminating slowly...")
	time.Sleep(time.Duration(5) * time.Second)
	os.Exit(0)
}
英文:

While the select answer is definitely the best, I just want to add that if you would have flipped the logic, the result would have been probably OK:

package main

import (
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;time&quot;
)

func main() {
	c := make(chan os.Signal)
	signal.Notify(c, os.Interrupt)
	go func() {
		for {
			fmt.Println(&quot;boop&quot;)
			time.Sleep(time.Duration(1) * time.Second)
		}
	}()

	&lt;-c
	fmt.Println(&quot;Terminating slowly...&quot;)
	time.Sleep(time.Duration(5) * time.Second)
	os.Exit(0)
}

huangapple
  • 本文由 发表于 2022年5月2日 17:36:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/72084866.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定