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 ~