The Complete Guide to "Respectful Farewell" in Go Development
In the Go world, writing a running program is easy, but writing a program that can gracefully exit is an art.
Today, we will exit from the "Hello, World" level, fight monsters and upgrade all the way, until we clear the multi-level assembly line and exit this ultimate boss!
🌱 1、 The most primitive exit: leave at will, regardless of the life or close of the employees
package main
import (
"fmt"
"time"
)
func main() {
// Launch a backend 'worker'
go func() {
for {
fmt.Println("I am silently moving bricks...")
time.Sleep(1 * time.Second)
}
}()
time.Sleep(3 * time.Second)
fmt.Println("The boss said he's off work!")
// The main program exits directly, forcing the workers to 'evaporate'
}
output
I am silently moving bricks...
I am silently moving bricks...
I am silently moving bricks...
The boss said he's off work!
(Program ends, backend goroutine mercilessly killed)
❌ Problem: The backend task may be writing files, making requests, saving databases... As soon as you leave, he will be "injured"!
✅ 2、 Beginner elegance: Say hello with a channel
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan bool)
go func() {
for {
select {
case <-done:
fmt.Println("Received notice of leaving work, currently tidying up the desktop ..")
return // Dignified exit
default:
fmt.Println("Continue moving bricks ..")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(3 * time.Second)
close(done) // Send the 'off work' signal
time.Sleep(1 * time.Second) // Wait for it to finish tidying up
fmt.Println("Everyone off work, close the door!")
}
output:
Continue moving bricks ..
Continue moving bricks ..
Continue moving bricks ..
Received notice of leaving work, currently tidying up the desktop ..
Everyone off work, close the door!
✅ Progress has been made! But time. Sleep (1 * time. Second) is too casual - what if it takes 2 seconds to tidy up?
🧱 3、 Intermediate Elegance: Use WaitGroup to wait for everyone to finish work
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
done := make(chan bool)
// Recruiting 3 workers
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for {
select {
case <-done:
fmt.Printf("Employee% d: Received! Turning off the computer ..\n", id)
return
default:
fmt.Printf("Worker% d: Moving bricks ..\n", id)
time.Sleep(800 * time.Millisecond)
}
}
}(i)
}
time.Sleep(3 * time.Second)
close(done)
wg.Wait() // Be patient and wait for everyone to turn off their computers
fmt.Println("The office lights are off, so elegant!")
}output:
Worker 1: Moving bricks...
Worker 2: Moving bricks...
Worker 3: Moving bricks...
...
Employee 2: Received! Turning off the computer...
Employee 1: Received! Turning off the computer...
Employee 3: Received! Turning off the computer...
The office lights are off, so elegant!
✅ Steady! But in the real world, programs often don't quit on their own - users will press Ctrl+C, K8s will send SIGTERM!
📡 4、 Real world: Monitor system signals (Ctrl+C doesn't panic)
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// Create a signal receiver
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
done := make(chan bool)
go func() {
for {
select {
case <-done:
fmt.Println("Background task: Received instruction, saving progress ..")
return
default:
fmt.Println("Background task: Running ..")
time.Sleep(1 * time.Second)
}
}
}()
fmt.Println("The program has started. Press Ctrl+C to exit gracefully")
<-sigCh // Block waiting signal(比如 Ctrl+C)
fmt.Println("\Detected exit signal! Prepare a dignified farewell ..")
close(done)
time.Sleep(1 * time.Second) // Simple waiting (will be optimized later)
fmt.Println("Goodbye, world! 👋")
}
Operation:
$ go run main.go
The program has started. Press Ctrl+C to exit gracefully
Background task: Running ..
^C
Detected exit signal! Prepare a dignified farewell ..
Background task: Received instruction, saving progress ..
Goodbye, world! 👋
✅ Finally, it's like a production environment! But Time Sleep is still not professional enough
🌟 5、 Go official recommendation: Use context unified management to cancel
Context is the "Swiss Army Knife" of Go concurrent programming, especially suitable for transmitting cancellation signals.
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Employee% d: Received cancellation instruction, reason:% v, exiting ..\n", id, ctx.Err())
return
default:
fmt.Printf("Worker% d: Working hard ..\n", id)
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Activate 3 workers
for i := 1; i <= 3; i++ {
go worker(ctx, i)
}
// Monitor system signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
fmt.Println("\N is ready to gracefully exit ..")
cancel() //Notify all employees
//Waiting (should be combined with WaitGroup in actual projects)
time.Sleep(2 * time.Second)
fmt.Println("Everyone evacuate safely!")
}
✅ Standard practice! However, please note that the semantics of context are 'cancel as soon as possible', and there is no guarantee that the remaining tasks will be processed!
🚨 6、 High energy warning: the trap of "data jamming" in the assembly line!
Assuming you have such a three-stage assembly line:
Producer → [10 intermediate workers] → [3 final consumers]
If all goroutines listen to ctx. Done() and exit immediately, any unprocessed data in the channel will be lost!
💥 This is the 'pseudo elegant exit' - superficially dignified, but actually losing data!
🏆 7、 Ultimate solution: two-stage exit+pipeline emptying
We need to achieve:
- Stop production (source cut off)
- Let the assembly line run naturally (clear the channel)
- Not losing a single data
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
func main() {
//Two level buffer channel
ch1 := make(chan int, 100) // Save original tasks
ch2 := make(chan int, 100) // Save processing results
var wg sync.WaitGroup
// ========== Phase 1: Producer (unique response exit signal)==========
stopProducing := make(chan struct{})
wg.Add(1)
go func() {
defer wg.Done()
defer close(ch1) // Production is completed, the channel is closed, and downstream is notified that there are no new activities
for taskID := 1; taskID <= 50; taskID++ {
select {
case <-stopProducing:
fmt.Println("Producer: Received a shutdown order and will no longer accept new orders!")
return
case ch1 <- taskID:
fmt.Printf("Producer: Release task% d \ n", taskID)
time.Sleep(50 * time.Millisecond)
}
}
fmt.Println("Producer: All tasks for today have been released")
}()
// ========== Phase 2: 10 intermediate workers (do not respond to cancellation! Exit only by closing the channel)==========
stage1Wg := &sync.WaitGroup{}
stage1Wg.Add(10)
for i := 1; i <= 10; i++ {
go func(id int) {
defer stage1Wg.Done()
//Key: Do not listen for any cancellation signals here!
//As long as ch1 is not turned off, keep working
for task := range ch1 {
result := task * 2
fmt.Printf("Intermediate worker% d: Processing task% d → Output% d \ n", id, task, result)
ch2 <- result
}
fmt.Printf("Middle worker% d: ch1 has been turned off, off work! \n", id)
}(i)
}
// After all the intermediate workers are finished, close ch2
go func() {
stage1Wg.Wait()
close(ch2)
fmt.Println("All intermediate workers are off work, ch2 is closed!")
}()
// ========== Phase Three: Three Final Consumers ==========
wg.Add(3)
for i := 1; i <= 3; i++ {
go func(id int) {
defer wg.Done()
// Similarly, relying solely on range to automatically exit
for result := range ch2 {
fmt.Printf("Final consumer% d: Received finished product% d", id, result)
time.Sleep(30 * time.Millisecond)
}
fmt.Printf("End consumer% d: No new products, work is done! \n", id)
}(i)
}
// ========== Monitor system exit signal ==========
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
fmt.Println("\n⚠️ Received system exit signal!")
//Only notify producers to stop work, * * do not forcibly interrupt workers**
close(stopProducing)
// ========== Waiting for the entire assembly line to be emptied==========
fmt.Println("Waiting for the assembly line to clear all tasks...")
wg.Wait()
fmt.Println("✅ All tasks have been processed, and the program exits with dignity!")
}
Simulate the output of pressing Ctrl+C halfway:
Producer: Release Task 1
Intermediate worker 3: Processing task 1 → Output 2
Final Consumer 1: Received Finished Product 2
...
...
Producer: Release Task 18
^C
⚠️ Received system exit signal!
Producer: Received a shutdown order and will no longer accept new orders!
Intermediate worker 5: Processing task 18 → Output 36
Final Consumer 2: Received Finished Product 36
...
Middle worker 1: ch1 has been turned off, off work!
All intermediate workers are off work, ch2 is closed!
End consumer 3: No new products, work is done!
✅ All tasks have been processed, and the program exits with dignity!✅ Perfect! Even if stopped midway, it ensures:
- No more accepting new tasks
- All accepted tasks have been completed
- No panic, no leakage of goroutines
🛡️ 8、 Anti freeze fallback: add a 'timeout insurance'
Although we hope to empty it, what if a task gets stuck? Add a 30 second timeout:
// Add timeout protection before wg. Wait()
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
fmt.Println("Elegant exit successful!")
case <-time.After(30 * time.Second):
fmt.Println("❌ Time out! Forced exit (there may be unprocessed data)")
os.Exit(1)
}
🎉 Conclusion: Elegance is a form of cultivation
In the world of Go, graceful exit is not about whether it is possible or not, but about willingness or not. Spending an extra 10 lines of code can prevent online accidents, data loss, and midnight alerts - this wave is not a loss!
Go

Go language error handling

Tell me about your understanding of Go

How strong is the Kratos that ordinary people can handle?
Go

As a PHP developer, I wrote a desktop application using Go for the first time

I have tried many Go frameworks, why did I ultimately choose this one? True fragrance
