+2

Bật mí từ khóa defer trong Golang: Trợ thủ đắc lực cho việc dọn dẹp mã nguồn

Trong Golang, defer là một từ khóa đặc biệt được sử dụng để trì hoãn việc thực thi của một hàm cho đến khi hàm bao quanh nó (surrounding function) chuẩn bị kết thúc (return).

Bạn có thể hiểu đơn giản: Khi bạn gắn defer trước một lời gọi hàm, Go sẽ ghi chú lại lệnh đó và tự động nói rằng "À, hãy nhớ chạy hàm này ngay trước khi thoát ra khỏi hàm hiện tại nhé".


image.png

1. Cơ chế hoạt động của defer

Để làm chủ defer, bạn chỉ cần nhớ 2 quy tắc cốt lõi sau:

  • Đánh giá ngay lập tức, thực thi sau cùng: Các tham số (arguments) truyền vào hàm defer sẽ được tính toán và chốt giá trị ngay tại thời điểm dòng lệnh defer được khai báo, nhưng bản thân hàm đó sẽ chỉ thực sự chạy khi hàm chính chuẩn bị return.
  • Nguyên tắc LIFO (Last-In, First-Out): Nếu bạn có nhiều lệnh defer trong cùng một hàm, chúng sẽ được đưa vào một ngăn xếp (stack). Lệnh defer nào được khai báo sau cùng sẽ được đẩy ra thực thi đầu tiên (Vào sau - Ra trước).

2. Ví dụ minh họa LIFO

Hãy xem đoạn code dưới đây để thấy rõ cách các hàm được trì hoãn và gọi ngược lại:

package main

import "fmt"

func main() {
    fmt.Println("1. Bắt đầu hàm main")

    defer fmt.Println("2. Defer đầu tiên")
    defer fmt.Println("3. Defer thứ hai")
    defer fmt.Println("4. Defer thứ ba")

    fmt.Println("5. Kết thúc hàm main")
}

Kết quả in ra màn hình sẽ là:

1. Bắt đầu hàm main
5. Kết thúc hàm main
4. Defer thứ ba
3. Defer thứ hai
2. Defer đầu tiên

Như bạn thấy, các lệnh defer được thực thi ngược từ dưới lên trên ngay sau khi logic chính của hàm đã chạy xong.


3. Tại sao defer lại quan trọng?

defer thường được sử dụng nhiều nhất cho các tác vụ dọn dẹp tài nguyên (clean-up). Việc đặt lệnh dọn dẹp ngay sát bên dưới dòng code khởi tạo tài nguyên giúp bạn không bao giờ quên đóng chúng, ngay cả khi hàm có nhiều nhánh return phức tạp hay xảy ra lỗi (panic) giữa chừng.

Những trường hợp sử dụng kinh điển nhất bao gồm:

  • Đóng file an toàn: defer file.Close() ngay sau khi gọi lệnh mở file thành công.
  • Mở/Khóa Mutex (Locks): Gọi mu.Lock() rồi gọi ngay defer mu.Unlock() để đảm bảo biến luôn được mở khóa, tránh lỗi deadlock.
  • Đóng kết nối Database/Mạng: Đảm bảo trả lại tài nguyên và bộ nhớ cho hệ thống ngay khi các luồng truy vấn kết thúc.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí