Skip to content

🔒 Singleton Pattern Creational

Một instance, được đảm bảo. Nhưng đừng lạm dụng — Singleton thường là code smell.

Học cách implement thread-safe với Meyers' Singleton (C++11 Magic Statics).

Vấn đề Singleton

Khi nào Sử dụng (Hiếm khi!)

┌─────────────────────────────────────────────────────────────────────────┐
│                    SINGLETON: CÁC TRƯỜNG HỢP SỬ DỤNG                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ✅ TRƯỜNG HỢP HỢP LỆ (Giới hạn):                                      │
│   ──────────────────────────────                                        │
│   • Logger — Một hệ thống log cho mỗi process                           │
│   • Configuration — Một trình quản lý cấu hình                          │
│   • Hardware Access — Một interface driver GPU                          │
│   • Thread Pool — Một pool chia sẻ                                      │
│                                                                         │
│   ❌ LẠM DỤNG (Đừng làm điều này):                                       │
│   ────────────────────────                                              │
│   • Database Connection — Sử dụng connection pool thay thế              │
│   • User Session — Truyền như tham số                                   │
│   • "Tôi cần truy cập toàn cục" — Dependency Injection thay thế         │
│                                                                         │
│   CẢNH BÁO:                                                             │
│   • Singleton = Global State = Shared Mutable State                     │
│   • Làm testing KHÓ KHĂN (không thể mock dễ dàng)                       │
│   • Tạo dependencies ẩn                                                 │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Tiến hóa của Singleton

Phiên bản 1: Cổ điển (Bị lỗi)

cpp
// ❌ KHÔNG THREAD-SAFE!
class Singleton {
public:
    static Singleton* Instance() {
        if (instance_ == nullptr) {        // Thread A kiểm tra: null
            instance_ = new Singleton();   // Thread A tạo
            // Thread B kiểm tra: null (A chưa hoàn thành!)
            // Thread B tạo THÊM MỘT instance nữa!
        }
        return instance_;
    }

private:
    Singleton() = default;
    static Singleton* instance_;
};

Singleton* Singleton::instance_ = nullptr;
┌─────────────────────────────────────────────────────────────────────────┐
│                    DÒNG THỜI GIAN RACE CONDITION                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Thời gian  Thread A                    Thread B                       │
│   ────       ─────────────────────────   ─────────────────────────      │
│   T0         if (instance_ == nullptr)                                  │
│   T1         // true, vào block          if (instance_ == nullptr)      │
│   T2         new Singleton()             // true, vào block             │
│   T3         // đang khởi tạo...         new Singleton()                │
│   T4         instance_ = ptr_a           // đang khởi tạo...            │
│   T5                                     instance_ = ptr_b              │
│                                                                         │
│   → HAI instances được tạo!                                             │
│   → ptr_a bị rò rỉ (không bao giờ được delete)                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Phiên bản 2: Double-Checked Locking (Phức tạp)

cpp
// ❌ PHỨC TẠP VÀ VẪN RỦI RO!
class Singleton {
public:
    static Singleton* Instance() {
        if (instance_ == nullptr) {                    // Kiểm tra lần 1 (không lock)
            std::lock_guard<std::mutex> lock(mutex_);
            if (instance_ == nullptr) {                // Kiểm tra lần 2 (có lock)
                instance_ = new Singleton();
            }
        }
        return instance_;
    }

private:
    static Singleton* instance_;
    static std::mutex mutex_;
};

// Vấn đề:
// 1. Vấn đề memory ordering (cần std::atomic + memory_order)
// 2. Phức tạp để làm đúng
// 3. Compiler reordering có thể phá vỡ nó

Phiên bản 3: Meyers' Singleton (Đúng!)

cpp
// ✅ THREAD-SAFE, LAZY, CHÍNH XÁC
class Logger {
public:
    // Xóa copy/move
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    
    static Logger& Instance() {
        static Logger instance;  // Kì diệu! Thread-safe từ C++11
        return instance;
    }
    
    void Log(const std::string& message) {
        std::lock_guard<std::mutex> lock(mutex_);
        std::cout << "[LOG] " << message << std::endl;
    }

private:
    Logger() {
        std::cout << "Logger được khởi tạo" << std::endl;
    }
    
    ~Logger() {
        std::cout << "Logger được hủy" << std::endl;
    }
    
    std::mutex mutex_;
};

// Sử dụng
Logger::Instance().Log("Ứng dụng đã khởi động");

Giải thích C++11 Magic Statics

┌─────────────────────────────────────────────────────────────────────────┐
│                    MAGIC STATICS (C++11)                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   C++11 Standard §6.7:                                                  │
│   "Nếu điều khiển vào khai báo đồng thời trong khi biến đang            │
│    được khởi tạo, thực thi đồng thời sẽ chờ cho đến khi                 │
│    khởi tạo hoàn thành."                                                │
│                                                                         │
│   Những gì compiler tạo ra:                                             │
│   ──────────────────────────────                                        │
│                                                                         │
│   static Logger& Instance() {                                           │
│       // Compiler thêm biến guard ẩn                                    │
│       static bool __guard = false;                                      │
│       static char __storage[sizeof(Logger)];                            │
│                                                                         │
│       if (!__guard) {                                                   │
│           // Compiler sử dụng atomic/lock dựa trên platform             │
│           __cxa_guard_acquire(&__guard);  // Lock                       │
│           new (__storage) Logger();        // Construct                 │
│           __guard = true;                                               │
│           __cxa_guard_release(&__guard);  // Unlock                     │
│       }                                                                 │
│       return *reinterpret_cast<Logger*>(__storage);                     │
│   }                                                                     │
│                                                                         │
│   → Khởi tạo thread-safe được xử lý bởi compiler                        │
│   → Không overhead sau lần gọi đầu tiên                                 │
│   → Khởi tạo lazy (chỉ khi sử dụng lần đầu)                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Ví dụ Hoàn chỉnh: Logger Thread-Safe

cpp
// logger.hpp
#pragma once
#include <string>
#include <mutex>
#include <fstream>
#include <chrono>
#include <iomanip>
#include <sstream>

enum class LogLevel { DEBUG, INFO, WARN, ERROR };

class Logger {
public:
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    
    static Logger& Instance() {
        static Logger instance;
        return instance;
    }
    
    void SetLevel(LogLevel level) {
        std::lock_guard<std::mutex> lock(mutex_);
        level_ = level;
    }
    
    void SetFile(const std::string& path) {
        std::lock_guard<std::mutex> lock(mutex_);
        if (file_.is_open()) file_.close();
        file_.open(path, std::ios::app);
    }
    
    void Log(LogLevel level, const std::string& message) {
        if (level < level_) return;
        
        std::lock_guard<std::mutex> lock(mutex_);
        
        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);
        
        std::ostringstream oss;
        oss << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S")
            << "] [" << LevelToString(level) << "] " << message;
        
        std::cout << oss.str() << std::endl;
        if (file_.is_open()) {
            file_ << oss.str() << std::endl;
        }
    }
    
    void Debug(const std::string& msg) { Log(LogLevel::DEBUG, msg); }
    void Info(const std::string& msg)  { Log(LogLevel::INFO, msg); }
    void Warn(const std::string& msg)  { Log(LogLevel::WARN, msg); }
    void Error(const std::string& msg) { Log(LogLevel::ERROR, msg); }

private:
    Logger() : level_(LogLevel::INFO) {}
    ~Logger() { if (file_.is_open()) file_.close(); }
    
    static const char* LevelToString(LogLevel level) {
        switch (level) {
            case LogLevel::DEBUG: return "DEBUG";
            case LogLevel::INFO:  return "INFO";
            case LogLevel::WARN:  return "WARN";
            case LogLevel::ERROR: return "ERROR";
        }
        return "UNKNOWN";
    }
    
    std::mutex mutex_;
    LogLevel level_;
    std::ofstream file_;
};

// Macro tiện lợi
#define LOG_DEBUG(msg) Logger::Instance().Debug(msg)
#define LOG_INFO(msg)  Logger::Instance().Info(msg)
#define LOG_WARN(msg)  Logger::Instance().Warn(msg)
#define LOG_ERROR(msg) Logger::Instance().Error(msg)

Cách sử dụng

cpp
int main() {
    Logger::Instance().SetLevel(LogLevel::DEBUG);
    Logger::Instance().SetFile("/var/log/myapp.log");
    
    LOG_INFO("Ứng dụng đã khởi động");
    LOG_DEBUG("Thông tin debug");
    LOG_WARN("Thông báo cảnh báo");
    LOG_ERROR("Đã xảy ra lỗi");
    
    return 0;
}

Testing Code Singleton

⚠️ VẤN ĐỀ TESTING

Singleton làm unit testing KHÓ KHĂN vì:

  1. Global state tồn tại giữa các test
  2. Không thể inject mock implementations
  3. Test failures phụ thuộc thứ tự

Giải pháp: Dependency Injection

cpp
// ✅ CÁCH TIẾP CẬN CÓ THỂ TEST: Inject logger như dependency
class PaymentService {
public:
    // Inject logger interface
    explicit PaymentService(ILogger& logger) : logger_(logger) {}
    
    void ProcessPayment(double amount) {
        logger_.Info("Đang xử lý thanh toán: $" + std::to_string(amount));
        // ... logic
    }

private:
    ILogger& logger_;
};

// Production: sử dụng Singleton
PaymentService service(Logger::Instance());

// Testing: sử dụng mock
MockLogger mockLogger;
PaymentService testService(mockLogger);
EXPECT_CALL(mockLogger, Info(HasSubstr("Đang xử lý")));
testService.ProcessPayment(100.0);

Thực hành Tốt nhất

┌─────────────────────────────────────────────────────────────────────────┐
│                    THỰC HÀNH TỐT NHẤT SINGLETON                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ✅ NÊN LÀM                                                            │
│   ─────                                                                 │
│   • Sử dụng Meyers' Singleton (static local)                            │
│   • Xóa copy/move constructors                                          │
│   • Sử dụng mutex cho truy cập state thread-safe                        │
│   • Cân nhắc cho Singleton accessor trả về interface                    │
│                                                                         │
│   ❌ KHÔNG NÊN LÀM                                                      │
│   ───────                                                               │
│   • Đừng sử dụng cho "truy cập toàn cục tiện lợi"                       │
│   • Đừng sử dụng Double-Checked Locking (dùng Meyers')                  │
│   • Đừng tạo nhiều Singletons phụ thuộc lẫn nhau                        │
│   • Đừng sử dụng Singleton nếu bạn cần testability                      │
│                                                                         │
│   💡 PHƯƠNG ÁN THAY THẾ                                                 │
│   ─────────────────                                                     │
│   • Dependency Injection (ưu tiên cho testability)                      │
│   • Service Locator (nếu DI quá phức tạp)                               │
│   • Monostate (single state, nhiều instances)                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Bước tiếp theo

📡 Observer → — Pub/Sub Pattern với std::function