Golang使用信号处理Unix命令

If you have no critics, you will likely have no success.

Accepting and processing signals from the operating system is important for various use cases in applications.

While many server-side languages have complicated or tedious approaches to processing signals from the OS, with Golang applications it’s extremely intuitive. Golang’s in-built OS package provides an easy way to integrate and react to Unix signals from your Go application. Let’s see how.

The Premise

Let’s say we want to build a Golang application that when requested to shutdown prints a message saying, “Thank you for using Golang.” Let’s set up the main function that basically keeps doing some work until an exit command is provided to the application.

1
2
3
4
5
6
func main() {
for {
fmt.Println("Doing Work")
time.Sleep(1 * time.Second)
}
}

When you run this application and kill it by providing a kill signal from your OS (Ctrl + C or Ctrl + Z, in most cases), you may see an output similar to this one:

1
2
3
4
Doing Work
Doing Work
Doing Work
Process finished with exit code 2

Now, we would like to interpret this kill signal within the Golang application and process it to print out the required exit message.

Receiving Signals

We will create a channel to receive the command from the OS. The OS package provides the Signal interface to handle signals and has OS-specific implementations.

1
killSignal := make(chan os.Signal, 1)

To notify killSignal, we use the Notify function provided by the signal package. The first parameter takes a channel of a os.Signal, while the next parameters accept a list of OS signals we want to notify our channel with.

1
signal.Notify(killSignal, os.Interrupt)

Alternatively, we can notify our signal with specific commands using the syscall package.

1
signal.Notify(killSignal, syscall.SIGINT, syscall.SIGTERM)

In order to process the signal, we’ll make our main function block wait for the interrupt signal using the killSignal channel. On receiving a command from the OS, we’ll print the exit message and kill the application.

In order to process our work loop, let’s move that into a separate goroutine using an anonymous function.

1
2
3
4
5
6
go func() {
for {
fmt.Println("Doing Work")
time.Sleep(1 * time.Second)
}
}()

While the work function runs in a separate routine, the main function will wait for the killSignal and print the exit message before exiting.

1
2
<-killSignal
fmt.Println("Thanks for using Golang!")

The Code

With all the components put together, the final code is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

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

func main() {

killSignal := make(chan os.Signal, 1)
signal.Notify(killSignal, os.Interrupt)
go func() {
for {
fmt.Println("Doing Work")
time.Sleep(1 * time.Second)
}
}()
<-killSignal
fmt.Println("Thanks for using Golang!")
}

On running this, it keeps executing the work loop, and upon receiving an interrupt signal from the OS, it prints the required message and then exits.

1
2
3
4
Doing Work
Doing Work
Doing Work
Thanks for using Golang!

Conclusion

This simple example can be extrapolated to handle many real-life scenarios, such as gracefully shutting down servers and receiving commands in command-line applications.

https://betterprogramming.pub/using-signals-to-handle-unix-commands-in-golang-f09e9efb7769