sync.Once bảo đảm một đoạn khởi tạo chạy đúng một lần dù có bao nhiêu goroutine gọi tới. Kiểm tra bằng biến bool thường là sai: hai goroutine có thể cùng thấy false và cùng khởi tạo.
Điểm quan trọng thứ hai: Do chặn cho tới khi lần gọi đầu tiên trả về. Nên mọi goroutine tới sau đều thấy trạng thái đã khởi tạo xong, không có chuyện dùng phải object đang xây dở.
var (
once sync.Once
client *redis.Client
)
func Client() *redis.Client {
once.Do(func() { client = redis.NewClient(opts) })
return client
}Nếu hàm panic: tài liệu sync nói rõ — Do coi như hàm đã chạy xong, các lần gọi sau sẽ không chạy lại. Panic được truyền lên cho người gọi đầu tiên, nhưng nếu người đó recover, mọi lời gọi tiếp theo sẽ nhận về một object khởi tạo dở mà không có cảnh báo nào.
Vì vậy: chỉ đặt trong Once những việc không thể fail (dựng struct, compile regexp hằng). Việc có thể lỗi và cần thử lại (mở kết nối DB, đọc file config) thì dùng mutex cộng cờ trạng thái để còn retry được. Go 1.21 bổ sung sync.OnceValue / OnceValues cho trường hợp khởi tạo có trả về giá trị.