Singleton Utilities Guide
Overview
The Singleton Utilities API in Hermes Shared Memory (HSHM) provides multiple singleton patterns optimized for different use cases, including thread safety, cross-device compatibility, and performance requirements. These utilities enable global state management across complex applications and shared memory systems.
Singleton Variants
Basic Singleton (Thread-Safe)
#include "hermes_shm/util/singleton.h"
class DatabaseConfig {
public:
std::string connection_string;
int max_connections;
DatabaseConfig() {
connection_string = "localhost:5432";
max_connections = 100;
}
void Configure(const std::string& host, int max_conn) {
connection_string = host;
max_connections = max_conn;
}
};
// Thread-safe singleton access
DatabaseConfig* config = hshm::Singleton<DatabaseConfig>::GetInstance();
config->Configure("prod-db:5432", 200);
// Multiple access from different threads
void worker_thread() {
DatabaseConfig* cfg = hshm::Singleton<DatabaseConfig>::GetInstance();
printf("Connecting to: %s\n", cfg->connection_string.c_str());
}
Lockfree Singleton (High Performance)
class MetricsCollector {
std::atomic<size_t> counter_;
public:
MetricsCollector() : counter_(0) {}
void Increment() {
counter_.fetch_add(1, std::memory_order_relaxed);
}
size_t GetCount() const {
return counter_.load(std::memory_order_relaxed);
}
};
// High-performance singleton without locking overhead
void hot_path_function() {
auto* metrics = hshm::LockfreeSingleton<MetricsCollector>::GetInstance();
metrics->Increment(); // Very fast, no locks
}
Cross-Device Singleton
class GPUManager {
public:
int device_count;
std::vector<int> available_devices;
GPUManager() {
device_count = GetGPUCount();
InitializeDevices();
}
private:
int GetGPUCount();
void InitializeDevices();
};
// Works on both host and GPU code
HSHM_CROSS_FUN
void initialize_cuda_context() {
GPUManager* gpu_mgr = hshm::CrossSingleton<GPUManager>::GetInstance();
printf("Found %d GPU devices\n", gpu_mgr->device_count);
}
// Lockfree version for GPU performance
HSHM_CROSS_FUN
void gpu_kernel_function() {
auto* gpu_mgr = hshm::LockfreeCrossSingleton<GPUManager>::GetInstance();
// Access without locking overhead in GPU kernels
}
Global Singleton (Eager Initialization)
class Logger {
public:
std::ofstream log_file;
std::mutex log_mutex;
Logger() {
log_file.open("/var/log/application.log", std::ios::app);
printf("Logger initialized during program startup\n");
}
void Log(const std::string& message) {
std::lock_guard<std::mutex> lock(log_mutex);
log_file << "[" << GetTimestamp() << "] " << message << std::endl;
}
private:
std::string GetTimestamp();
};
// Initialized immediately when program starts
Logger* logger = hshm::GlobalSingleton<Logger>::GetInstance();
void application_function() {
// Logger already exists and is ready
hshm::GlobalSingleton<Logger>::GetInstance()->Log("Function called");
}
Platform-Aware Global Singleton
class NetworkManager {
public:
std::string local_hostname;
std::vector<std::string> network_interfaces;
NetworkManager() {
DiscoverNetworkInterfaces();
printf("Network manager initialized\n");
}
private:
void DiscoverNetworkInterfaces();
};
// Automatically chooses best implementation for platform
HSHM_CROSS_FUN
void network_operation() {
auto* net_mgr = hshm::GlobalCrossSingleton<NetworkManager>::GetInstance();
printf("Local hostname: %s\n", net_mgr->local_hostname.c_str());
}
C-Style Global Variable Singletons
Basic Global Variables
// Header declaration
HSHM_DEFINE_GLOBAL_VAR_H(DatabaseConfig, g_db_config);
// Source file definition
HSHM_DEFINE_GLOBAL_VAR_CC(DatabaseConfig, g_db_config);
// Usage
void configure_database() {
DatabaseConfig* config = HSHM_GET_GLOBAL_VAR(DatabaseConfig, g_db_config);
config->Configure("prod:5432", 500);
}
Cross-Platform Global Variables
class SharedMemoryPool {
public:
size_t pool_size;
void* memory_base;
SharedMemoryPool() : pool_size(0), memory_base(nullptr) {
InitializePool();
}
private:
void InitializePool();
};
// Header - works on host and device
HSHM_DEFINE_GLOBAL_CROSS_VAR_H(SharedMemoryPool, g_memory_pool);
// Source file
HSHM_DEFINE_GLOBAL_CROSS_VAR_CC(SharedMemoryPool, g_memory_pool);
// Usage in cross-platform code
HSHM_CROSS_FUN
void allocate_from_pool(size_t size) {
SharedMemoryPool* pool = HSHM_GET_GLOBAL_CROSS_VAR(SharedMemoryPool, g_memory_pool);
// Allocation logic here
}
Pointer-Based Global Variables
class TaskScheduler {
public:
std::queue<std::function<void()>> task_queue;
std::mutex queue_mutex;
std::condition_variable queue_cv;
bool running;
TaskScheduler() : running(true) {
printf("Task scheduler created\n");
}
void SubmitTask(std::function<void()> task);
void ProcessTasks();
void Shutdown();
};
// Header - pointer version for lazy initialization
HSHM_DEFINE_GLOBAL_PTR_VAR_H(TaskScheduler, g_task_scheduler);
// Source file
HSHM_DEFINE_GLOBAL_PTR_VAR_CC(TaskScheduler, g_task_scheduler);
// Usage - automatically creates instance on first access
void submit_work() {
TaskScheduler* scheduler = HSHM_GET_GLOBAL_PTR_VAR(TaskScheduler, g_task_scheduler);
scheduler->SubmitTask([]() {
printf("Task executing\n");
});
}
Cross-Platform Pointer Variables
class DeviceMemoryManager {
public:
size_t total_memory;
size_t available_memory;
std::map<void*, size_t> allocations;
DeviceMemoryManager() {
QueryDeviceMemory();
}
private:
void QueryDeviceMemory();
};
// Header
HSHM_DEFINE_GLOBAL_CROSS_PTR_VAR_H(DeviceMemoryManager, g_device_memory);
// Source file
HSHM_DEFINE_GLOBAL_CROSS_PTR_VAR_CC(DeviceMemoryManager, g_device_memory);
// Cross-platform usage
HSHM_CROSS_FUN
void* allocate_device_memory(size_t size) {
DeviceMemoryManager* mgr = HSHM_GET_GLOBAL_CROSS_PTR_VAR(DeviceMemoryManager, g_device_memory);
// Device-specific allocation
return nullptr; // Implementation specific
}
Macro Wrappers for Global Variable Singletons
Simplifying Access with Macros
For frequently used singletons, create convenient macro wrappers to reduce code verbosity and provide cleaner API access:
// Define convenient macros for common singletons
#define DATABASE_CONFIG hshm::Singleton<DatabaseConfig>::GetInstance()
#define METRICS_COLLECTOR hshm::LockfreeSingleton<MetricsCollector>::GetInstance()
#define GPU_MANAGER hshm::CrossSingleton<GPUManager>::GetInstance()
#define LOGGER hshm::GlobalSingleton<Logger>::GetInstance()
#define NETWORK_MANAGER hshm::GlobalCrossSingleton<NetworkManager>::GetInstance()
// Global variable style macros
#define MEMORY_POOL HSHM_GET_GLOBAL_VAR(SharedMemoryPool, g_memory_pool)
#define TASK_SCHEDULER HSHM_GET_GLOBAL_PTR_VAR(TaskScheduler, g_task_scheduler)
#define DEVICE_MEMORY HSHM_GET_GLOBAL_CROSS_PTR_VAR(DeviceMemoryManager, g_device_memory)
Usage Examples with Macros
Before - Verbose singleton access:
void configure_system() {
// Verbose and repetitive
hshm::Singleton<DatabaseConfig>::GetInstance()->Configure("prod:5432", 500);
hshm::LockfreeSingleton<MetricsCollector>::GetInstance()->Increment();
hshm::GlobalSingleton<Logger>::GetInstance()->Log("System configured");
// Long variable declarations
auto* gpu_mgr = hshm::CrossSingleton<GPUManager>::GetInstance();
auto* net_mgr = hshm::GlobalCrossSingleton<NetworkManager>::GetInstance();
}
After - Clean macro access:
void configure_system() {
// Clean and concise
DATABASE_CONFIG->Configure("prod:5432", 500);
METRICS_COLLECTOR->Increment();
LOGGER->Log("System configured");
// Short, readable access
GPU_MANAGER->device_count;
NETWORK_MANAGER->local_hostname;
}
Recommended Macro Naming Conventions
// 1. SCREAMING_SNAKE_CASE for singleton instances
#define CONFIG_MANAGER hshm::Singleton<ConfigManager>::GetInstance()
#define CACHE_MANAGER hshm::LockfreeSingleton<CacheManager>::GetInstance()
// 2. Prefix with component name for large applications
#define DB_CONNECTION_POOL hshm::Singleton<ConnectionPool>::GetInstance()
#define DB_QUERY_CACHE hshm::LockfreeSingleton<QueryCache>::GetInstance()
// 3. Use descriptive names that match functionality
#define THREAD_POOL hshm::GlobalSingleton<ThreadPoolManager>::GetInstance()
#define ERROR_REPORTER hshm::CrossSingleton<ErrorReporter>::GetInstance()
// 4. For global variables, match the variable name pattern
#define SHARED_BUFFER HSHM_GET_GLOBAL_VAR(SharedBuffer, g_shared_buffer)
#define TEMP_ALLOCATOR HSHM_GET_GLOBAL_PTR_VAR(TempAllocator, g_temp_alloc)
Advanced Macro Patterns
Conditional Access Macros:
// Macro with null check for optional singletons
#define SAFE_LOGGER (LOGGER ? LOGGER : &null_logger_instance)
// Debug-only singleton access
#ifdef DEBUG
#define DEBUG_PROFILER hshm::Singleton<Profiler>::GetInstance()
#else
#define DEBUG_PROFILER (&null_profiler_instance)
#endif
Functional Macros:
// Macro that performs common operations
#define LOG_INFO(msg) LOGGER->Log(LogLevel::INFO, msg)
#define LOG_ERROR(msg) LOGGER->Log(LogLevel::ERROR, msg)
#define INCREMENT_COUNTER(name) METRICS_COLLECTOR->IncrementCounter(name)
#define RECORD_LATENCY(name, duration) METRICS_COLLECTOR->RecordLatency(name, duration)
Type-Safe Wrapper Macros:
// Wrapper with type checking
#define GET_CONFIG(type) \
(static_cast<type*>(hshm::Singleton<ConfigRegistry>::GetInstance()->Get(#type)))
// Usage: auto* db_cfg = GET_CONFIG(DatabaseConfig);
Best Practices for Singleton Macros
- Consistency: Use the same naming convention across your entire codebase
- Documentation: Document what each macro expands to and its thread safety guarantees
- Scope: Place macro definitions in a central header file included by all modules
- Namespace: Consider using a prefix to avoid naming conflicts
- Type Safety: Ensure macros maintain type safety and don't hide important type information
- Debugging: Make macros debugger-friendly - avoid complex expressions
- Performance: Use appropriate singleton type (lockfree vs thread-safe) based on usage patterns
Header File Organization
// singletons.h - Central singleton definitions
#ifndef PROJECT_SINGLETONS_H
#define PROJECT_SINGLETONS_H
#include "hermes_shm/util/singleton.h"
#include "config/database_config.h"
#include "metrics/metrics_collector.h"
#include "logging/logger.h"
// Define all singleton access macros
#define DATABASE_CONFIG hshm::Singleton<DatabaseConfig>::GetInstance()
#define METRICS_COLLECTOR hshm::LockfreeSingleton<MetricsCollector>::GetInstance()
#define LOGGER hshm::GlobalSingleton<Logger>::GetInstance()
// Functional convenience macros
#define LOG_INFO(msg) LOGGER->Info(msg)
#define LOG_ERROR(msg) LOGGER->Error(msg)
#define COUNT(metric) METRICS_COLLECTOR->Increment(metric)
#endif // PROJECT_SINGLETONS_H
Best Practices
- Thread Safety: Use
Singleton<T>for thread-safe access,LockfreeSingleton<T>only with thread-safe types - Cross-Platform Code: Use
CrossSingleton<T>andGlobalCrossSingleton<T>for code that runs on both host and device - Python Compatibility: Avoid standard singletons in code called by Python; use global variables instead
- Eager vs Lazy: Use
GlobalSingleton<T>for resources needed at startup, regular singletons for lazy initialization - Resource Management: Implement proper destructors and cleanup in singleton classes
- Configuration: Use singletons for application-wide configuration and settings
- Performance: Use lockfree variants in performance-critical paths with appropriate atomic types
- Memory Management: Be aware that singletons live for the entire program duration
- Testing: Design singleton classes to be testable by allowing dependency injection where possible
- Documentation: Document singleton lifetime and thread safety guarantees for each singleton class
- Macro Wrappers: Create convenient macro wrappers for frequently accessed singletons to improve code readability
- Naming Conventions: Use consistent SCREAMING_SNAKE_CASE naming for singleton access macros