"지연"방식으로 Ctrl + C 신호를 캡처하고 정리 기능을 실행할 수 있습니까?


207

콘솔에서 보낸 Ctrl+C( SIGINT) 신호 를 캡처하고 부분 실행 합계를 인쇄하고 싶습니다 .

골랑에서 가능합니까?

참고 : 내가 먼저 질문을 게시 할 때 나는에 대해 혼란스러워했다 Ctrl+C되는 SIGTERM대신 SIGINT.

답변:


260

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

프로그램을 종료하고 정보를 인쇄하는 방식은 전적으로 귀하에게 달려 있습니다.


감사! 그래서 ... ^ C 그렇다면 SIGTERM이 아니십니까? 업데이트 : 죄송합니다. 제공 한 링크가 충분히 상세합니다!
Sebastián Grignoli 2016 년

19
대신 이 이전 답변과 같이 for sig := range g {사용할 수도 있습니다 <-sigchan. stackoverflow.com/questions/8403862/…
Denys Séguret

3
@dystroy : 물론, 첫 번째 신호에 대한 응답으로 프로그램을 실제로 종료하려는 경우. 루프를 사용 하면 프로그램을 종료 하지 않기로 결정한 경우 모든 신호를 포착 할 수 있습니다 .
Lily Ballard 2016 년

79
참고 : 실제로 작동하려면 프로그램을 빌드해야합니다. go run콘솔을 통해 프로그램을 실행하고 ^ C를 통해 SIGTERM을 보내면 신호가 채널에 기록되고 프로그램이 응답하지만 예기치 않게 루프에서 빠지는 것처럼 보입니다. SIGRERM도 사용하기 때문입니다 go run! (이것은 나 원인 상당한 혼란이있다!)
윌리엄 Pursell

5
고 루틴이 신호를 처리하기 위해 프로세서 시간을 확보하려면 메인 고 루틴이 차단 작업을 호출하거나 runtime.Gosched적절한 위치 (프로그램의 기본 루프에있는 경우)에서 호출해야합니다.
misterbee

107

이것은 작동합니다 :

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

1
다른 독자들을 위해 : 왜 os.Interrupt와 syscall.SIGTERM을 잡으려고하는지에 대한 설명은 @adamonduty의 답변을보십시오. 특히 그가 몇 달 전에 게시 한 이후로이 답변에 그의 설명을 포함시키는 것이 좋았을 것입니다.
Chris

1
왜 비 차단 채널을 사용하고 있습니까? 이것이 필요합니까?
Awn

4
@Barry 왜 버퍼 크기가 1이 아닌 2입니까?
pdeva

2
다음은 문서 에서 발췌 한 것입니다 . "패키지 신호는 c 로의 전송을 차단하지 않습니다. 호출자는 c에 예상 신호 속도를 유지할 수있는 충분한 버퍼 공간이 있는지 확인해야합니다. 단 하나의 신호 값을 알리는 데 사용되는 채널의 경우 1 크기의 버퍼이면 충분합니다."
bmdelacruz

25

다른 답변에 약간 더하기 위해 실제로 SIGTERM (kill 명령으로 전송 된 기본 신호)을 잡으려면 syscall.SIGTERMos.Interrupt 대신 사용할 수 있습니다 . syscall 인터페이스는 시스템에 따라 다르며 모든 곳에서 작동하지 않을 수 있습니다 (예 : Windows). 그러나 두 가지를 모두 잘 잡을 수 있습니다.

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

7
signal.Notify기능을 사용하면 한 번에 여러 신호를 지정할 수 있습니다. 따라서 코드를로 단순화 할 수 있습니다 signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen

나는 게시 후 그것을 발견했다고 생각합니다. 결정된!
adamlamar

2
os.Kill은 어떻습니까?
Awn

2
@Eclipse 좋은 질문입니다! os.Kill 대응 하는 syscall.Kill송신하지만 잡힌되지 않을 수있는 신호이다. 명령과 같습니다 kill -9 <pid>. 캐치 kill <pid>하고 정상적으로 종료하려면을 사용해야 syscall.SIGTERM합니다.
adamlamar

@adamlamar Ahh, 그 말이 맞습니다. 감사!
Awn

18

위의 허용 된 답변에 하나 또는 두 개의 작은 오타가 있었으므로 여기에 정리 된 버전이 있습니다. 이 예에서는 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)                                                     
  }                                                                
}()    

2
고 루틴이 신호를 처리하기 위해 프로세서 시간을 확보하려면 메인 고 루틴이 차단 작업을 호출하거나 runtime.Gosched적절한 위치 (프로그램의 기본 루프에있는 경우)에서 호출해야합니다.
misterbee

8

위의 모든 내용은 접속했을 때 작동하는 것처럼 보이지만 gobyexample의 신호 페이지 에는 신호를 캡처하는 데 대한 깨끗하고 완전한 예가 있습니다. 이 목록에 추가 할 가치가 있습니다.


0

Death 는 채널과 대기 그룹을 사용하여 종료 신호를 기다리는 간단한 라이브러리입니다. 신호가 수신되면 정리하려는 모든 구조체에서 close 메소드를 호출합니다.


3
4 줄의 코드로 무엇을 할 수 있는가? (허용 된 답변에 따라)
Jacob

표준 클로즈 인터페이스를 가지고 있다면 그것들을 모두 병렬로 정리하고 자동으로 구조체를 닫을 수 있습니다.
Ben Aldrich

0

당신은 syscall.SIGINT 및 syscall.SIGTERM 신호를 감지하는 다른 goroutine을 가지고 사용 채널로 중계 할 수 signal.Notify을 . 채널을 사용하여 해당 고 루틴에 후크를 보내고 함수 슬라이스에 저장할 수 있습니다. 채널에서 종료 신호가 감지되면 슬라이스에서 해당 기능을 실행할 수 있습니다. 자원을 정리하고 실행중인 goroutines가 완료 될 때까지 기다리거나 데이터를 유지하거나 부분 실행 합계를 인쇄하는 데 사용할 수 있습니다.

종료시 후크를 추가하고 실행하는 작고 간단한 유틸리티를 작성했습니다. 도움이 되길 바랍니다.

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

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
}

메인 함수를 청소 해야하는 경우 go func ()을 사용하여 메인 스레드에서 신호를 캡처해야합니다.


-2

누군가가 Windows에서 신호를 처리하는 방법이 필요한 경우에만 기록하십시오. os / exec를 통해 prog A를 호출하는 prog A에서 처리 해야하는 요구 사항이 있었지만 ex를 통해 신호를 전송하기 때문에 prog B는 결코 정상적으로 종료되지 못했습니다. cmd.Process.Signal (syscall.SIGTERM) 또는 기타 신호는 Windows에서 지원되지 않습니다. 내가 처리 한 방법은 신호 파일로 임시 파일을 만드는 것입니다. prog A와 prog B를 통한 .signal.term은 해당 파일이 간격베이스에 있는지 확인해야합니다. 파일이 존재하면 파일이 프로그램을 종료하고 필요한 경우 정리를 처리합니다. 다른 방법이 있지만 확실하게 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.