Go 的 defer 理解

28 阅读1分钟

背景

在开发过程中,经常发现 defer xxx(),具体干什么用的呢?

概念

defer 用于在 go 中进行尾调工作,比如资源回收,延迟执行等

执行逻辑

  1. 在父函数的 return 语句执行之后,返回值之前执行,或者 panic 后
  2. 多个 defer,采用的栈顺序执行,LIFO
package main

import "fmt"

func example1() {
    fmt.Println("开始")
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")
    fmt.Println("结束")
}

func main() {
    example1()
}

输出

开始
结束
defer 3
defer 2
defer 1

panic 后执行

package main

import "fmt"

func example6() {
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    
    fmt.Println("准备 panic")
    panic("出错了!")
    
    defer fmt.Println("defer 3")  // 不会执行
    fmt.Println("这行不会执行")
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获 panic:", r)
        }
    }()
    
    example6()
}

输出

准备 panic
defer 2
defer 1
捕获 panic: 出错了!

配合 context 的使用:幂等函数 + defer = 防弹式资源管理

package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

func worker(ctx context.Context, id int, wg *sync.WaitGroup) {
	defer wg.Done()

	for {
		select {
		case <-ctx.Done():
			fmt.Printf("Worker %d stopping: %v\n", id, ctx.Err())
			return
		default:
			fmt.Printf("Worker %d processing...\n", id)
			time.Sleep(500 * time.Millisecond)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	var wg sync.WaitGroup

	defer cancel()
	// 启动5个 worker
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go worker(ctx, i, &wg)
	}

	// 运行3秒后停止所有 worker
	time.Sleep(3 * time.Second)
	fmt.Println("Stopping all workers...")
	cancel() // 一次调用,停止所有 worker

	wg.Wait()
	fmt.Println("All workers stopped")
}