Chuyển đến nội dung chính
Mô-đun 3 · Bài học 5 trong tổng số 515/50 trong khóa học~12 min
Bài học theo mô-đun (5/5)

`defer`: đảm bảo dọn dẹp

defer trì hoãn việc thực hiện cuộc gọi cho đến khi có chức năng kèm theo trả về. Nó chạy ngay cả khi hàm thoát do hoảng loạn. Nhiều defer trong cùng một chức năng, thực hiện theo thứ tự LIFO (Vào sau, Xuất trước).

Đó là cơ chế đặc trưng của việc dọn dẹp tài nguyên: đóng các tập tin, mở khóa mutexes, giải phóng kết nối, khôi phục trạng thái.

Cú pháp và trường hợp sử dụng chuẩn

Go
f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close()   // chiamata SEMPRE all'uscita della funzione

// ... usa f liberamente ...
return nil

Điển hình: f.Close(), mu.Unlock(), db.Close(), tx.Rollback(), recover().

Các đối số được đánh giá ngay lập tức, việc thực thi bị hoãn lại

Khi bạn viết defer foo(x), x được đánh giá tại thời điểm defer, không phải lúc thực hiện:

Go
x := 10
defer fmt.Println(x)   // cattura 10
x = 99
// quando la funzione ritorna stampa "10", non "99"

Lệnh LIFO

Trì hoãn xếp chồng lên nhau và thực thi bắt đầu với cái gần đây nhất:

Go
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
// output: 3, 2, 1

Điều này phù hợp với ngữ nghĩa tài nguyên: cái được mở cuối cùng là cái đầu tiên phải đóng cửa.

defer + recover: xử lý sự hoảng loạn

defer là cách duy nhất để chặn panic:

Go
func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recuperato:", r)
        }
    }()
    panic("boom")
}

Cạm bẫy: defer trong một vòng lặp

Đặt defer f.Close() vào một vòng lặp sẽ tích lũy thời gian trì hoãn **cho đến khi kết thúc hàm **, không phải của mỗi lần lặp. Trong một vòng lặp dài, bạn mở nhiều tập tin và chỉ đóng chúng khi chức năng thoát: nguy cơ rò rỉ bộ mô tả tệp.

Go
// MALE: defer accumulati
for _, path := range paths {
    f, _ := os.Open(path)
    defer f.Close()   // chiude TUTTI alla fine della funzione esterna
    // ...
}

// MEGLIO: estrai in una funzione
for _, path := range paths {
    elabora(path)   // ogni chiamata ha il proprio scope di defer
}

Hãy thử nó

tập thể dục#go.m3.l5.e1
Nỗ lực: 0Đang tải…

In 'inizio', sau đó in 'fine' bằng defer, sau đó in 'mezzo'. Đầu ra phải là: inizio/mezzo/fine.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Trì hoãn chạy khi main() sắp quay trở lại.

Giải pháp khả dụng sau 3 lần thử

tập thể dục#go.m3.l5.e2
Nỗ lực: 0Đang tải…

Viết ba số trì hoãn liên tiếp in 1, 2 và 3 theo thứ tự đó trong mã. Đầu ra sẽ là 3/2/1 (LIFO).

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Ba lần trì hoãn theo trình tự; hãy nhớ rằng họ thực hiện theo thứ tự ngược lại.

Giải pháp khả dụng sau 3 lần thử

Quiz#go.m3.l5.e3
Sẵn sàng

Cái này in cái gì? (lưu ý: đối số trì hoãn được đánh giá ngay lập tức)

Go
x := 10
defer fmt.Println(x)
x = 99
Tùy chọn trả lời

Tóm tắt lại

  • defer call() trì hoãn việc thực thi cho đến khi hàm trả về.
  • Nhiều lần trì hoãn = lệnh LIFO.
  • Các đối số được đánh giá ngay lập tức, việc thực thi bị hoãn lại.
  • Được sử dụng để dọn dẹp xác định: tập tin, mutexes, kết nối.
  • defer + recover() chặn panic (hiếm, ở ranh giới).
  • Không bao giờ defer bên trong một vòng lặp dài: nó sẽ tích lũy cho đến khi hàm bên ngoài trả về.