是否可以捕获Ctrl+C信号(SIGINT)并以“defer”方式运行清理函数?

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

Is it possible to capture a Ctrl+C signal (SIGINT) and run a cleanup function, in a "defer" fashion?

问题

我想要捕获从控制台发送的<kbd>Ctrl</kbd>+<kbd>C</kbd> (SIGINT)信号,并打印出一些部分运行总数。

英文:

I want to capture the <kbd>Ctrl</kbd>+<kbd>C</kbd> (SIGINT) signal sent from the console and print out some partial run totals.

答案1

得分: 339

你可以使用 os/signal 包来处理传入的信号。<kbd>Ctrl</kbd>+<kbd>C</kbd> 是 SIGINT,所以你可以使用它来捕获 os.Interrupt

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
    for sig := range c {
        // sig 是一个 ^C,进行处理
    }
}()

你可以自行决定如何使你的程序终止并打印信息。

英文:

You can use the os/signal package to handle incoming signals. <kbd>Ctrl</kbd>+<kbd>C</kbd> is SIGINT, so you can use this to trap os.Interrupt.

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
    for sig := range c {
        // sig is a ^C, handle it
    }
}()

The manner in which you cause your program to terminate and print information is entirely up to you.

答案2

得分: 145

package main

import (
"fmt"
"os"
"os/signal"
"syscall"
"time" // or "runtime"
)

func cleanup() {
fmt.Println("cleanup")
}

func main() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cleanup()
os.Exit(1)
}()

for {
    fmt.Println("sleeping...")
    time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
}

}

Playground中查看

英文:

This works:

<!-- lang: go-lang -->

package main

import (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;os/signal&quot;
    &quot;syscall&quot;
    &quot;time&quot; // or &quot;runtime&quot;
)

func cleanup() {
    fmt.Println(&quot;cleanup&quot;)
}

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        &lt;-c
        cleanup()
        os.Exit(1)
    }()

    for {
        fmt.Println(&quot;sleeping...&quot;)
        time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
    }
}

Checkout in the Playground

答案3

得分: 31

为了稍微补充其他答案,如果你真的想捕获SIGTERM信号(kill命令发送的默认信号),你可以使用syscall.SIGTERM代替os.Interrupt。请注意,syscall接口是特定于系统的,可能不适用于所有地方(例如在Windows上)。但它可以很好地捕获两者:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
英文:

To add slightly to the other answers, if you actually want to catch SIGTERM (the default signal sent by the kill command), you can use syscall.SIGTERM in place of os.Interrupt. Beware that the syscall interface is system-specific and might not work everywhere (e.g. on windows). But it works nicely to catch both:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....

答案4

得分: 23

// 捕获 Ctrl+C 并停止 CPU 分析器
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
log.Printf("捕获到 %v,停止分析器并退出..", sig)
pprof.StopCPUProfile()
os.Exit(1)
}
}()

英文:

There were (at time of posting) one or two little typos in the accepted answer above, so here's the cleaned up version. In this example I'm stopping the CPU profiler when receiving <kbd>Ctrl</kbd>+<kbd>C</kbd>.

// capture ctrl+c and stop CPU profiler                            
c := make(chan os.Signal, 1)                                       
signal.Notify(c, os.Interrupt)                                     
go func() {                                                        
  for sig := range c {                                             
    log.Printf(&quot;captured %v, stopping profiler and exiting..&quot;, sig)
    pprof.StopCPUProfile()                                         
    os.Exit(1)                                                     
  }                                                                
}()    

答案5

得分: 13

package main

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

func main() {
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}

Source: gobyexample.com/signals

英文:

All of the above seem to work when spliced in, but gobyexample's signals page has a really clean and complete example of signal capturing. Worth adding to this list.

package main

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

func main() {
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := &lt;-sigs
        fmt.Println()
        fmt.Println(sig)
        done &lt;- true
    }()

    fmt.Println(&quot;awaiting signal&quot;)
    &lt;-done
    fmt.Println(&quot;exiting&quot;)
}

Source: gobyexample.com/signals

答案6

得分: 2

查看示例

当我们运行这个程序时,它会阻塞等待一个信号。通过键入<kbd>Ctrl</kbd>+<kbd>C</kbd>(终端显示为^C),我们可以发送一个SIGINT信号,导致程序打印出interrupt并退出。

signal.Notify注册给定的通道以接收指定信号的通知。

package main

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

func main() {

	sig := make(chan os.Signal, 1)
	done := make(chan bool, 1)

	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		sig := <-sig
		fmt.Println()
		fmt.Println(sig)
		done <- true

		fmt.Println("ctrl+c")
	}()

	fmt.Println("awaiting signal")
	<-done
	fmt.Println("exiting")
}

检测HTTP请求取消

package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {

	mux := http.NewServeMux()
	mux.HandleFunc("/path", func(writer http.ResponseWriter, request *http.Request) {

		time.Sleep(time.Second * 5)

		select {
		case <-time.After(time.Millisecond * 10):
			fmt.Println("started")
			return
		case <-request.Context().Done():
			fmt.Println("canceled")
		}
	})

	http.ListenAndServe(":8000", mux)

}
英文:

look at the example

When we run this program it will block waiting for a signal. By typing <kbd>Ctrl</kbd>+<kbd>C</kbd> (which the terminal shows as ^C) we can send a SIGINT signal, causing the program to print interrupt and then exit.

signal. Notify registers the given channel to receive notifications of the specified signals.

package main

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

func main() {

	sig := make(chan os.Signal, 1)
	done := make(chan bool, 1)

	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		sig := &lt;-sig
		fmt.Println()
		fmt.Println(sig)
		done &lt;- true

		fmt.Println(&quot;ctrl+c&quot;)
	}()

	fmt.Println(&quot;awaiting signal&quot;)
	&lt;-done
	fmt.Println(&quot;exiting&quot;)
}

detect HTTP request cancel



package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

func main() {

	mux := http.NewServeMux()
	mux.HandleFunc(&quot;/path&quot;, func(writer http.ResponseWriter, request *http.Request) {

		time.Sleep(time.Second * 5)

		select {
		case &lt;-time.After(time.Millisecond * 10):

			fmt.Println(&quot;started&quot;)
			return
		case &lt;-request.Context().Done():
			fmt.Println(&quot;canceled&quot;)
		}
	})

	http.ListenAndServe(&quot;:8000&quot;, mux)

}

答案7

得分: 0

你可以创建一个不同的goroutine来检测syscall.SIGINT和syscall.SIGTERM信号,并使用signal.Notify将它们转发到一个通道中。你可以使用一个通道将一个hook发送给那个goroutine,并将其保存在一个函数切片中。当通道上检测到关闭信号时,你可以执行切片中的这些函数。这可以用于清理资源、等待正在运行的goroutine完成、持久化数据或打印部分运行总数。

我写了一个小而简单的实用程序来在关闭时添加和运行hook。希望它能有所帮助。

https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go

你可以以"defer"的方式来做这个。

例如,优雅地关闭服务器的示例:

srv := &http.Server{}

go_shutdown_hook.ADD(func() {
	log.Println("正在关闭服务器")
	srv.Shutdown(nil)
	log.Println("服务器已关闭")
})
 
l, err := net.Listen("tcp", ":3090")

log.Println(srv.Serve(l))

go_shutdown_hook.Wait()
英文:

You can have a different goroutine that detects syscall.SIGINT and syscall.SIGTERM signals and relay them to a channel using signal.Notify. You can send a hook to that goroutine using a channel and save it in a function slice. When the shutdown signal is detected on the channel, you can execute those functions in the slice. This can be used to clean up the resources, wait for running goroutines to finish, persist data, or print partial run totals.

I wrote a small and simple utility to add and run hooks at shutdown. Hope it can be of help.

https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go

You can do this in a 'defer' fashion.

example for shutting down a server gracefully :

srv := &amp;http.Server{}

go_shutdown_hook.ADD(func() {
	log.Println(&quot;shutting down server&quot;)
	srv.Shutdown(nil)
	log.Println(&quot;shutting down server-done&quot;)
})
 
l, err := net.Listen(&quot;tcp&quot;, &quot;:3090&quot;)

log.Println(srv.Serve(l))

go_shutdown_hook.Wait()

答案8

得分: 0

这是另一个版本,适用于需要清理一些任务的情况。代码将在它们的方法中保留清理过程。

package main

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

func main() {
    _, done1 := doSomething1()
    _, done2 := doSomething2()

    // 执行主线程

    println("等待完成")
    <-done1
    <-done2
    fmt.Print("清理完成,可以安全退出")
}

func doSomething1() (error, chan bool) {
    // 做一些事情
    done := make(chan bool)
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        // 清理 something1
        done <- true
    }()
    return nil, done
}

func doSomething2() (error, chan bool) {
    // 做一些事情
    done := make(chan bool)
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        // 清理 something2
        done <- true
    }()
    return nil, done
}

如果需要清理主函数,您需要在主线程中使用go func()来捕获信号。

英文:

This is another version which work in case you have some tasks to cleanup. Code will leave clean up process in their method.

package main

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



func main() {
	
	_,done1:=doSomething1()
	_,done2:=doSomething2()
 
	//do main thread
	
	
	println(&quot;wait for finish&quot;)
	&lt;-done1
	&lt;-done2
	fmt.Print(&quot;clean up done, can exit safely&quot;)
	
}

func doSomething1() (error, chan bool) {
	//do something
	done:=make(chan bool)
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		&lt;-c
		//cleanup of something1
		done&lt;-true
	}()
	return nil,done
}


func doSomething2() (error, chan bool) {
	//do something
	done:=make(chan bool)
	c := make(chan os.Signal, 2)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
	go func() {
		&lt;-c
		//cleanup of something2
		done&lt;-true
	}()
	return nil,done
}

in case you need to clean main function you need to capture signal in main thread using go func() as well.

答案9

得分: -1

Death 是一个简单的库,它使用通道和等待组来等待关闭信号。一旦接收到信号,它将调用您想要清理的所有结构体上的关闭方法。

英文:

Death is a simple library that uses channels and a wait group to wait for shutdown signals. Once the signal has been received it will then call a close method on all of your structs that you want to cleanup.

答案10

得分: -4

只是为了记录,如果有人需要在Windows上处理信号的方法。

我有一个要求,即通过os/exec从程序A调用程序B,但是程序B从未能够优雅地终止,因为在Windows上不支持通过cmd.Process.Signal(syscall.SIGTERM)或其他信号发送信号。

我通过创建一个临时文件作为信号来解决了这个问题。例如,程序A创建文件.signal.term,程序B需要在一定时间间隔内检查该文件是否存在。如果文件存在,它将退出程序并进行必要的清理。

我相信还有其他方法,但这个方法能够完成工作。

英文:

Just for the record if somebody needs a way to handle signals on Windows.

I had a requirement to handle from program A calling program B through os/exec but program B never was able to terminate gracefully because sending signals through cmd.Process.Signal(syscall.SIGTERM) or other signals are not supported on Windows.

I handled this problem by creating a temp file as a signal. For example, program A creates file .signal.term and program B needs to check if that file exists on interval base. If file exists it will exit the program and handle a cleanup if needed.

I'm sure there are other ways but this did the job.

huangapple
  • 本文由 发表于 2012年6月30日 05:05:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/11268943.html
匿名

发表评论

匿名网友

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

确定