Có thể bắt được tín hiệu Ctrl + C và chạy chức năng dọn dẹp, theo kiểu thời gian trì hoãn không?


207

Tôi muốn thu tín hiệu Ctrl+C( SIGINT) được gửi từ bàn điều khiển và in ra một số tổng số chạy một phần.

Điều này có thể ở Golang không?

Lưu ý: Khi tôi lần đầu tiên đăng câu hỏi, tôi đã bối rối về Ctrl+Cviệc SIGTERMthay vì SIGINT.

Câu trả lời:


259

Bạn có thể sử dụng gói os / signal để xử lý các tín hiệu đến. Ctrl+ CSIGINT , vì vậy bạn có thể sử dụng cái này để bẫy 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ách thức khiến chương trình của bạn chấm dứt và in thông tin hoàn toàn phụ thuộc vào bạn.


Cảm ơn! Vì vậy, ... C không phải là SIGTERM, sau đó? CẬP NHẬT: Xin lỗi, liên kết bạn cung cấp đủ chi tiết!
Sebastián Grignoli

19
Thay vì for sig := range g {, bạn cũng có thể sử dụng <-sigchannhư trong câu trả lời trước: stackoverflow.com/questions/8403862/iêu
Denys Séguret

3
@dystroy: Chắc chắn, nếu bạn thực sự sẽ chấm dứt chương trình để đáp ứng với tín hiệu đầu tiên. Bằng cách sử dụng vòng lặp, bạn có thể bắt được tất cả các tín hiệu nếu bạn quyết định không chấm dứt chương trình.
Lily Ballard

79
Lưu ý: bạn thực sự phải xây dựng chương trình để làm việc này. Nếu bạn chạy chương trình qua go runbàn điều khiển và gửi SIGTERM qua ^ C, tín hiệu được ghi vào kênh và chương trình sẽ phản hồi, nhưng dường như bất ngờ rơi ra khỏi vòng lặp. Điều này là do SIGRERM go runcũng vậy! (Điều này đã gây cho tôi sự nhầm lẫn đáng kể!)
William Pursell

5
Lưu ý rằng để con goroutine có thời gian xử lý tín hiệu để xử lý tín hiệu, con goroutine chính phải gọi một thao tác chặn hoặc gọi runtime.Goschedở một nơi thích hợp (trong vòng lặp chính của chương trình của bạn, nếu có)
misterbee

107

Những công việc này:

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
Đối với những độc giả khác: Hãy xem câu trả lời của @adamonduty để biết lời giải thích về lý do tại sao bạn muốn bắt os.Interrupt và syscall.SIGTERM Rất vui khi đưa lời giải thích của anh ấy vào câu trả lời này, đặc biệt là khi anh ấy đăng vài tháng trước bạn.
Chris

1
Tại sao bạn sử dụng kênh không chặn? Điều này có cần thiết không?
Awn

4
@Barry tại sao kích thước bộ đệm 2 thay vì 1?
pdeva

2
Đây là một đoạn trích từ tài liệu . "Tín hiệu gói sẽ không chặn gửi đến c: người gọi phải đảm bảo rằng c có đủ không gian bộ đệm để theo kịp tốc độ tín hiệu dự kiến. Đối với một kênh được sử dụng để thông báo chỉ một giá trị tín hiệu, bộ đệm có kích thước 1 là đủ."
bmdelacruz

25

Để thêm một chút vào các câu trả lời khác, nếu bạn thực sự muốn bắt SIGTERM (tín hiệu mặc định được gửi bởi lệnh kill), bạn có thể sử dụng syscall.SIGTERMthay cho os.Interrupt. Xin lưu ý rằng giao diện tòa nhà là dành riêng cho hệ thống và có thể không hoạt động ở mọi nơi (ví dụ: trên windows). Nhưng nó hoạt động độc đáo để bắt cả hai:

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

7
Các signal.Notifychức năng cho phép để xác định một số tín hiệu cùng một lúc. Vì vậy, bạn có thể đơn giản hóa mã của bạn để signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen

Tôi nghĩ rằng tôi đã tìm thấy nó sau khi đăng. Đã sửa!
adamlamar

1
Thế còn os.Kill?
Awn

2
@Eclipse Câu hỏi tuyệt vời! os.Kill tương ứng để syscall.Kill, đó là một dấu hiệu cho thấy có thể được gửi nhưng không bắt được. Nó tương đương với lệnh kill -9 <pid>. Nếu bạn muốn bắt kill <pid>và tắt máy một cách duyên dáng, bạn phải sử dụng syscall.SIGTERM.
adamlamar

@adamlamar Ahh, điều đó có ý nghĩa. Cảm ơn!
Awn

18

Có (tại thời điểm đăng) một hoặc hai lỗi chính tả trong câu trả lời được chấp nhận ở trên, vì vậy đây là phiên bản đã được dọn sạch. Trong ví dụ này, tôi dừng trình cấu hình CPU khi nhận Ctrl+ 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
Lưu ý rằng để con goroutine có thời gian xử lý tín hiệu để xử lý tín hiệu, con goroutine chính phải gọi một thao tác chặn hoặc gọi runtime.Goschedở một nơi thích hợp (trong vòng lặp chính của chương trình của bạn, nếu có)
misterbee

8

Tất cả những điều trên dường như hoạt động khi được ghép vào, nhưng trang tín hiệu của gobyexample có một ví dụ thực sự rõ ràng và đầy đủ về việc thu tín hiệu. Đáng để thêm vào danh sách này.


0

Cái chết là một thư viện đơn giản sử dụng các kênh và một nhóm chờ để chờ tín hiệu tắt máy. Khi tín hiệu đã được nhận, nó sẽ gọi một phương thức đóng trên tất cả các cấu trúc của bạn mà bạn muốn dọn dẹp.


3
Vì vậy, nhiều dòng mã và một thư viện thư viện bên ngoài để làm những gì có thể được thực hiện trong bốn dòng mã? (theo câu trả lời được chấp nhận)
Jacob

nó cho phép bạn thực hiện dọn dẹp song song tất cả chúng và tự động đóng các cấu trúc nếu chúng có giao diện đóng tiêu chuẩn.
Ben Aldrich

0

Bạn có thể có một con goroutine khác nhau để phát hiện các tòa nhà cao tầng.SIGINT và syscall.SIGTERM và chuyển chúng đến một kênh bằng cách sử dụng signal.Notify . Bạn có thể gửi một cái móc đến con goroutine đó bằng cách sử dụng một kênh và lưu nó trong một lát chức năng. Khi tín hiệu tắt được phát hiện trên kênh, bạn có thể thực hiện các chức năng đó trong lát cắt. Điều này có thể được sử dụng để dọn sạch các tài nguyên, chờ cho chạy goroutines để hoàn thành, duy trì dữ liệu hoặc in tổng số chạy một phần.

Tôi đã viết một tiện ích nhỏ và đơn giản để thêm và chạy hook khi tắt máy. Hy vọng nó có thể giúp đỡ.

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

Bạn có thể làm điều này theo cách 'trì hoãn'.

ví dụ để tắt máy chủ một cách duyên dáng:

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

Đây là một phiên bản khác hoạt động trong trường hợp bạn có một số nhiệm vụ cần dọn dẹp. Mã sẽ để lại quá trình làm sạch trong phương pháp của họ.

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
}

trong trường hợp bạn cần làm sạch chức năng chính, bạn cũng cần thu tín hiệu trong luồng chính bằng cách sử dụng go func ().


-2

Chỉ dành cho bản ghi nếu ai đó cần một cách để xử lý tín hiệu trên Windows. Tôi có một yêu cầu xử lý từ prog A gọi prog B thông qua os / exec nhưng prog B không bao giờ có thể chấm dứt một cách duyên dáng vì gửi tín hiệu qua ex. cmd.Process.Signal (syscall.SIGTERM) hoặc các tín hiệu khác không được hỗ trợ trên Windows. Cách tôi xử lý là bằng cách tạo tệp tạm thời dưới dạng tín hiệu cũ. .signal.term thông qua prog A và prog B cần kiểm tra xem tệp đó có tồn tại trên cơ sở khoảng không, nếu tệp tồn tại, nó sẽ thoát khỏi chương trình và xử lý dọn dẹp nếu cần, tôi chắc chắn có những cách khác nhưng điều này đã làm được.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.