英文:
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 (
"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)
}
}
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 (
"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)
}
}
}
As soon as a signal is sent to channel c
, the non-default case becomes non-blocking and gets select
ed, 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 (
"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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论