Giao diện
🔒 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).
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ì:
- Global state tồn tại giữa các test
- Không thể inject mock implementations
- 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) │
│ │
└─────────────────────────────────────────────────────────────────────────┘