250

我想捕获从控制台发送的Ctrl+C( SIGINT) 信号并打印出一些部分运行总计。

这在 Golang 中可能吗?

注意:当我第一次发布这个问题时,我很困惑Ctrl+CSIGTERM不是SIGINT.

4

10 回答 10

309

您可以使用os/signal包来处理传入的信号。Ctrl+CSIGINT,因此您可以使用它来捕获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
    }
}()

您使程序终止和打印信息的方式完全取决于您。

于 2012-06-29T21:16:17.557 回答
125

这有效:

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
    }
}
于 2013-08-10T05:15:53.267 回答
29

稍微补充一下其他答案,如果您真的想捕获 SIGTERM(kill 命令发送的默认信号),您可以使用syscall.SIGTERMos.Interrupt 代替。请注意,系统调用接口是特定于系统的,可能无法在任何地方工作(例如在 Windows 上)。但它很好地抓住了两者:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
于 2013-05-14T08:08:34.777 回答
23

上面接受的答案中有(在发布时)一两个小错字,所以这里是清理后的版本。在此示例中,我在接收Ctrl+时停止 CPU 分析器C

// 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("captured %v, stopping profiler and exiting..", sig)
    pprof.StopCPUProfile()                                         
    os.Exit(1)                                                     
  }                                                                
}()    
于 2012-09-24T18:55:45.523 回答
11

以上所有内容在拼接时似乎都有效,但是gobyexample 的信号页面有一个非常干净和完整的信号捕获示例。值得添加到此列表中。

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

来源:gobyexample.com/signals

于 2015-02-20T13:01:31.000 回答
1

看例子

当我们运行这个程序时,它会阻塞等待信号。通过键入 ctrl-C(终端显示为 ^C),我们可以发送一个 SIGINT 信号,导致程序打印中断然后退出。

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

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)

}
于 2020-09-16T10:56:51.393 回答
0

您可以有一个不同的 goroutine 来检测 syscall.SIGINT 和 syscall.SIGTERM 信号并使用signal.Notify将它们中继到通道。您可以使用通道向该 goroutine 发送钩子并将其保存在函数切片中。当在通道上检测到关闭信号时,您可以在切片中执行这些功能。这可用于清理资源、等待运行的 goroutine 完成、保存数据或打印部分运行总数。

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

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

您可以以“延迟”方式执行此操作。

优雅关闭服务器的示例:

srv := &http.Server{}

go_shutdown_hook.ADD(func() {
    log.Println("shutting down server")
    srv.Shutdown(nil)
    log.Println("shutting down server-done")
})

l, err := net.Listen("tcp", ":3090")

log.Println(srv.Serve(l))

go_shutdown_hook.Wait()
于 2017-07-09T17:59:15.760 回答
0

这是另一个版本,可以在您有一些清理任务的情况下使用。代码将在他们的方法中留下清理过程。

package main

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

)



func main() {

    _,done1:=doSomething1()
    _,done2:=doSomething2()

    //do main thread


    println("wait for finish")
    <-done1
    <-done2
    fmt.Print("clean up done, can exit safely")

}

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() {
        <-c
        //cleanup of something1
        done<-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() {
        <-c
        //cleanup of something2
        done<-true
    }()
    return nil,done
}

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

于 2017-11-20T00:52:29.897 回答
-1

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

于 2015-03-24T03:06:35.060 回答
-4

如果有人需要一种在 Windows 上处理信号的方法,仅作记录。

我需要通过 os/exec 从程序 A 调用程序 B 进行处理,但程序 B 永远无法正常终止,因为cmd.Process.Signal(syscall.SIGTERM)Windows 不支持通过发送信号或其他信号。

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

我敢肯定还有其他方法,但这确实有效。

于 2017-10-02T17:06:44.227 回答