Singleton
Kiến thức nền tảng
Tình huống sử dụng thực tế
Cách implement chuẩn production (thread-safe, lazy, với DI)
Anti-pattern cần tránh
Benchmark và kiểm thử đơn vị
💡 1. Khi nào cần dùng Singleton?
✅ Dùng khi:
Chỉ cần một instance duy nhất trong toàn app (vd: config, logger, db connection pool)
Tránh khởi tạo lại đối tượng nặng hoặc tốn chi phí (vd: Redis pool, Kafka producer)
Đảm bảo thread-safe access trong concurrent Golang apps
❌ Không dùng khi:
Mỗi request cần state riêng biệt
Muốn dễ test unit (singleton khó mock nếu không inject được)
🧱 2. Các cách implement Singleton trong Go
⚙️ Cách 1: Thread-safe với sync.Once
(chuẩn production)
sync.Once
(chuẩn production)package config
import (
"sync"
)
type Config struct {
DatabaseURL string
Port int
}
var (
instance *Config
once sync.Once
)
func GetInstance() *Config {
once.Do(func() {
instance = &Config{
DatabaseURL: "postgres://localhost:5432/db",
Port: 8080,
}
})
return instance
}
🧠 Giải thích:
sync.Once
đảm bảo hàmDo
chỉ được chạy 1 lần, dù nhiều goroutine cùng gọiThread-safe tuyệt đối, không cần lock thủ công
Lazy-init: chỉ khởi tạo khi lần đầu được gọi
🧪 3. Test đơn vị singleton
func TestSingleton(t *testing.T) {
c1 := GetInstance()
c2 := GetInstance()
if c1 != c2 {
t.Error("Expected same instance, got different instances")
}
}
🔥 4. Tình huống thực tế
🧩 Use Case: Logger Service
package logger
import (
"log"
"os"
"sync"
)
type Logger struct {
*log.Logger
}
var (
instance *Logger
once sync.Once
)
func GetLogger() *Logger {
once.Do(func() {
instance = &Logger{
Logger: log.New(os.Stdout, "[APP] ", log.LstdFlags),
}
})
return instance
}
Sử dụng:
logger := logger.GetLogger()
logger.Println("Service started...")
🧨 5. Anti-pattern cần tránh
❌ Dùng init()
để khởi tạo Singleton:
init()
để khởi tạo Singleton:var instance = &Config{} // KHÔNG nên
Khởi tạo eager (luôn khởi tạo kể cả không dùng)
Không thread-safe nếu init phức tạp
Khó test/mock trong unit test
🔧 6. Singleton với Dependency Injection (Pro)
Kỹ thuật: inject singleton vào service layer
type DB struct {
Conn *sql.DB
}
type UserService struct {
db *DB
}
func NewUserService(db *DB) *UserService {
return &UserService{db: db}
}
Tại main.go
:
func main() {
db := database.GetInstance()
userSvc := NewUserService(db)
userSvc.DoSomething()
}
➡️ Điều này giúp dễ mock db
trong unit test thay vì hardcoded singleton trong UserService
.
📈 7. Benchmark sơ bộ
func BenchmarkGetInstance(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = GetInstance()
}
}
Với sync.Once
, chi phí truy xuất sau lần đầu là cực thấp (vài ns), không cần tối ưu thêm.
✅ Tổng kết checklist
Dùng sync.Once
để đảm bảo thread-safe lazy init
✅
Không khởi tạo trước bằng init()
✅
Inject singleton vào services để dễ test
✅
Tránh lưu state bên trong singleton (nếu có concurrency)
✅
Viết test để kiểm tra instance
✅
Last updated