This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

I believe that XDM who wrote Golang has written HTTP server bar, using Golang to write server is not very cool ah

The simplest HTTP server

Let’s write a simple HTTP server

func main(a) {
	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	http.ListenAndServe(": 9090", srvMux)

}

func getinfo(w http.ResponseWriter, r *http.Request) {
	fmt.Println("i am xiaomotong!!!")
	w.Write([]byte("you are access right!! \n"))}Copy the code

/ getInfo: / getInfo: / getInfo: / getInfo: / getInfo: / getInfo: / getInfo: / getInfo

# curl localhost:9090/getinfo
you are access right!!
Copy the code

It is clear that it can be accessed normally, and we will also get the corresponding information, and the log of the server is normal

Let’s think about this, if there is an accident, the program crashes, panic, or what we think is a kill, how can we determine how the server exits?

The server that joins the signal

We are familiar with signals when we write C/C++. In Golang, we also add signals to identify kill programs

In Linux, you can run man kill to view the detailed description of the kill command

Here we can see that a kill -9 is the corresponding SIGKILL signal. We also know that SIGINT is emitted when it is ctrL-C, which is also an interrupt signal. If you are not sure about this, you can search the Linux signal list on the Internet

func main(a) {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(": 9090", srvMux)

	 fmt.Println(<-sig)
}
Copy the code

ListenAndServe is blocked, so here we listen to the 9090 service is open a separate process

verify

# go run main.go
^Cinterrupt
Copy the code

So at this point, our HTTP server is already able to tell the signal, how does it exit

Our demand is slowly increasing, but in actual work, we definitely can’t do such CUO

Exit gracefully

At work, we have HTTP server, there must be other processing logic, such as reading and writing files, GRPC communication, or the use of database, then our program shut down, or according to the situation to deal with, to follow the atomicity

There are two situations as follows:

  • There are no strict quality requirements for data, and panic does not matter, so it is not a problem to shut down gracefully
  • For those who can manipulate the database, read and write files, etc., and modify the data, we do not expect the process of manipulating the data to be interrupted, we need to follow the atomicity, our program needs to provide a buffer time, to exit gracefully

A proper exit must be graceful

How do you make a graceful exit?

In the example above, when the main coroutine receives an interrupt signal, it immediately exits the program, and so does the subcoroutine

If we need the main coroutine to wait for the subcoroutine to finish processing its current work before exiting, don’t we need the main coroutine and the subcoroutine to talk to each other in order for that to happen?

Use 2 channels for elegant closure

That’s the easy way to think about it

The implementation can be roughly divided into two steps:

  • When the main coroutine receives an interrupt signal, it notifies the child coroutine to gracefully close, named stopCh
  • After receiving the notification, the subcoroutine completes the notification master coroutine closeCh at hand
func main(a) {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	stopCh := make(chan int)
	closeCh := make(chan int)

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(": 19999", srvMux)

	go func(stopCh, closeCh chan int) {
		for {
			select {
			case <-stopCh:
				fmt.Println(" processing tasks")
                // The simulation is processing data
				time.Sleep(time.Second*time.Duration(2))
				fmt.Println("process over ")
				closeCh <- 1
			}
		}
	}(stopCh, closeCh)

	<-sig
	stopCh <- 1
	<-closeCh
	fmt.Println("close server ")}Copy the code

Here we can see that two channels are used to allow the primary and subcoroutines to communicate with each other

Open up a coroutine and execute an anonymous function to listen for data on the stopCh channel. If there is data, the main coroutine receives a signal and notifies the child coroutine to gracefully close

At this point, the child coroutine finishes its work and writes data to closeCh to inform the main coroutine that it is ready to shut down the program as usual

Use nested channels

Graceful closure using nested channels may seem like a surprise, but the website gives us some directions

The realization idea is as follows:

  • Use a channel stopCh, and the element inside channel stopCh is another channel tmpCh
  • When the main coroutine receives an exit signal, it writes data tmpCh to stopCh and starts listening for data in tmpCh
  • When the child coroutine reads the data tmpCh from stopCh, it knows it needs to gracefully shut down, and when it has done its work, it writes to tmpCh
  • The main coroutine listens for data in tmpCh and exits the program
func main() {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	stopCh := make(chan chan struct{})

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(":19999", srvMux)

	go func(stopCh chan chan struct{}) {
		for {
			select {
			case tmpCh:=<-stopCh:
				fmt.Println(" processing tasks")
				time.Sleep(time.Second*time.Duration(2))
				fmt.Println("process over ")
				tmpCh <- struct{}{}
			}
		}
	}(stopCh)

	tmpCh := make(chan struct{})

	<-sig
	stopCh <- tmpCh
	<-tmpCh
	fmt.Println("close server ")
}
Copy the code

The above two methods are similar in that they use channels to gracefully close the function. Channels are golang’s natural data structure, so let’s use them

Use golang’s standard solution context

Using golang’s context is a better way to solve the problem of gracefully closing

Don’t think of context as just passing data, context can also control the life cycle of a subcoroutine

func main(a) {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	stopCh := make(chan struct{})
    // Create a context
	ctx,cancle := context.WithCancel(context.Background())

	srvMux := http.NewServeMux()
	srvMux.HandleFunc("/getinfo", getinfo)
	go http.ListenAndServe(": 19999", srvMux)

	go func(ctx context.Context,stopCh chan struct{}) {
		for {
			select {
			case <-ctx.Done():
				fmt.Println(" processing tasks")
				time.Sleep(time.Second*time.Duration(2))
				fmt.Println("process over ")
				stopCh <- struct{}{}
			}
		}
	}(ctx,stopCh)

	<-sig
	cancle()
	<-stopCh
	fmt.Println("close server ")}Copy the code

When the main coroutine closes the context, the subcoroutine reads data from the channel and closes gracefully. We can see in the source code that the return value of ctx.done () is also a channel

The main coroutine waits for all child coroutines to gracefully close the implementation method

All we’re talking about is the main coroutine waiting for one of its children to close gracefully, and then closing itself

In practice, there must be more than one coroutine, so we want to be elegant, so we want to be elegant to the end, and we want to do this by using golang context + sync.waitGroup

func main(a) {
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)
	ctx, cancle := context.WithCancel(context.Background())

	mywg := sync.WaitGroup{}
	// Control the declaration cycle of 5 subcoroutines
	mywg.Add(5)

	for i := 0; i < 5; i++ {
		go func(ctx context.Context) {
			defer mywg.Done()
			for {
				select {
				case <-ctx.Done():
					fmt.Println(" processing tasks")
					time.Sleep(time.Second * time.Duration(1))
					fmt.Println("process over ")
					return

				default:
					time.Sleep(time.Second * time.Duration(1))
				}
			}
		}(ctx)
	}

	<-sig
	cancle()
	// Wait for all subcoroutines to close gracefully
	mywg.Wait()
	fmt.Println("close server ")}Copy the code

In the code above, we use sync.waitGroup to control the life cycle of the five child coroutines, cancle() cancels the CTX when the main coroutine receives an interrupt signal

Each child coroutine reads the data from ctx.done () and does its own thing

Finally defer mywg.done (), and the main coroutine mywg.wait () closed its own program after all coroutines closed gracefully

Verify the effect of

# go run main.go
^C processing tasks
processing tasks
processing tasks
processing tasks
processing tasks
process over
process over
process over
process over
process over
close server
Copy the code

The above is a simple path from not graceful close to learn the common graceful close method, hope you useful oh

Welcome to like, follow and favorites

Friends, your support and encouragement, I insist on sharing, improve the quality of the power

All right, that’s it for this time

Technology is open, our mentality, should be more open. Embrace change, live in the sun, and strive to move forward.

I am Nezha, welcome to like, see you next time ~