Go Channels Fundamental Pattern #1: Indefinite Process
In Go, channel
is a simple type that can be used for sending and receiving data concurrently. We also have goroutines
, a lightweight thread managed by Go runtime. In this series of articles, we will discuss fundamental patterns of Go channels that are implemented by more complex concurrency patterns.
Indefinite process
An unbuffered channel receiver blocks a process until data is supplied to the channel.
package main
import "fmt"
func main() {
ch := make(chan struct{})
go func() {
for i := range 10 {
fmt.Println(i)
}
ch <- struct{}{}
}()
<-ch
fmt.Println("Counting done")
}
In the code above, ch
makes sure that the goroutine function will not exit before it finishes the loop. This is because the main
goroutine waits for channel receive, which happens after the loop.
If the channel never receives any data, the program will run indefinitely until the user closes the program.
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan struct{}{})
go func() {
for {
fmt.Println("ping")
time.Sleep(time.Second)
}
}()
<- ch
}
This program prints ping
every second indefinitely and will only exit with interrupts such as Ctrl+C
. To be exact, the program runs until SIGINT
or SIGTERM
is sent by the operating system.
Case study: Graceful shutdown
Furthermore, our program can respond to interrupts instead of directly exiting. Usually, it is related to cleanup or defer
operations, making graceful shutdown possible.
To do this, we need a buffered channel of 1 and signal.Notify
.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
go func() {
for {
fmt.Println("ping")
time.Sleep(time.Second)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("Prints before exiting")
}
Instead of exiting, the program listens to OS interrupts such as SIGINT
and SIGTERM
. That means, for example, if a user invokes Ctrl+C
to close the program, the main goroutine will continue to the next operations after <- quit
.
We can apply this pattern to make sure shutdown operations don’t break a system or leak data. For example, we can implement a graceful shutdown to a web server as follows.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := http.Server{
Addr: ":8080",
}
// Start a server at port 8080
// This goroutine will run indefinitely
// If any server error happens, the program will exit immediately
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// Graceful shutdown starts
// At this point, further interrupts will not be handled and are considered as forced shutdown
log.Println("shutdown server...")
// If next shutdown operation doesn't finished after 3 second, just exit the program
// This prevents indefinite shutdown time
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("server shutdown error: %v", err)
}
<-ctx.Done()
log.Println("Server exited gracefully")
}
Continue to Part 2
Comments
Use a GitHub account to create a comment. This page can be used to edit or delete comments.