Linux C语言实现线程安全单例模式(含动态注册和复杂结构体)
- 一、引言
- 二、基本的单例实现方式
- 1. 静态局部变量实现(C11线程安全)
- 2. 双重检查锁定模式(Double-Checked Locking)
- 三、封装复杂结构体(带文件输出的日志器)
- 1. 头文件设计(logger.h)
- 2. 实现文件(logger.c)
- 四、多线程测试用例
- 五、高级特性扩展
- 1. 支持多日志输出通道
- 2. 支持日志格式化器注册
- 3. 支持异步日志写入
- 六、性能优化建议
- 七、错误处理与健壮性
- 1、文件写入错误处理:
- 2、内存分配检查:
- 3、递归调用保护:
- 4、信号安全处理:
- 八、跨平台考虑
- 1、路径分隔符:
- 2、线程实现:
- 3、时间函数:
- 九、线程安全单例日志系统的逻辑图
- 1、系统逻辑图
- 2、关键模块交互流程图
- 3、扩展设计:插件式架构
- 4、性能优化对比表
- 5、错误处理状态机
- 6、跨平台适配层设计
- 7、测试用例增强
- 十、总结
一、引言
在系统软件或嵌入式开发中,我们常常需要某些模块在系统中仅有一个实例,并在多个线程中共享使用。这时候,就需要用到"单例模式"(Singleton Pattern)。
单例模式的核心目标是确保一个类只有一个实例,并提供一个全局访问点。在Linux C开发中,实现单例模式需要考虑以下几个关键点:
线程安全性
资源管理
可扩展性
性能影响
本文将深入探讨如何在Linux环境下用C语言实现一个线程安全的单例模式,包含动态注册功能和复杂结构体封装。
二、基本的单例实现方式
1. 静态局部变量实现(C11线程安全)
Logger* get_logger_instance() {static Logger instance = {0}; // C11标准保证线程安全的初始化return &instance;
}
优点:
简单直接
C11标准保证线程安全
自动管理生命周期
缺点:
不支持动态内存分配
初始化方式受限
难以实现复杂的初始化逻辑
2. 双重检查锁定模式(Double-Checked Locking)
Logger* get_logger_instance() {static Logger* instance = NULL;static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;if (instance == NULL) {pthread_mutex_lock(&mutex);if (instance == NULL) {instance = malloc(sizeof(Logger));// 初始化代码...}pthread_mutex_unlock(&mutex);}return instance;
}
优点:
支持动态内存分配
线程安全
延迟初始化
缺点:
代码复杂度稍高
需要手动管理资源
三、封装复杂结构体(带文件输出的日志器)
1. 头文件设计(logger.h)
#ifndef LOGGER_H
#define LOGGER_H#include <stddef.h>// 日志级别枚举
typedef enum {LOG_LEVEL_DEBUG,LOG_LEVEL_INFO,LOG_LEVEL_WARNING,LOG_LEVEL_ERROR,LOG_LEVEL_CRITICAL
} LogLevel;// 日志钩子函数类型
typedef void (*LogHook)(LogLevel level, const char* message, void* user_data);// 日志器配置结构体
typedef struct {const char* filename;LogLevel min_level;size_t max_file_size;int backup_count;
} LoggerConfig;// 日志器结构体
typedef struct Logger Logger;// 获取日志器实例
Logger* logger_get_instance(void);// 初始化日志器
int logger_initialize(const LoggerConfig* config);// 记录日志
void logger_log(LogLevel level, const char* format, ...);// 注册日志钩子
void logger_register_hook(LogHook hook, void* user_data);// 清理日志器
void logger_cleanup(void);#endif // LOGGER_H
2. 实现文件(logger.c)
#include "logger.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <errno.h>// 日志器内部结构体
struct Logger {FILE* log_file;char* filename;pthread_mutex_t mutex;LogLevel min_level;size_t max_file_size;int backup_count;LogHook hook;void* hook_user_data;
};// 静态单例实例
static Logger* g_instance = NULL;
static pthread_mutex_t g_instance_mutex = PTHREAD_MUTEX_INITIALIZER;// 内部函数声明
static int rotate_log_file(Logger* logger);
static const char* get_level_string(LogLevel level);
static void write_log_header(FILE* file, LogLevel level);// 获取日志器实例
Logger* logger_get_instance(void) {if (g_instance == NULL) {pthread_mutex_lock(&g_instance_mutex);if (g_instance == NULL) {g_instance = calloc(1, sizeof(Logger));if (g_instance) {pthread_mutex_init(&g_instance->mutex, NULL);g_instance->min_level = LOG_LEVEL_INFO;g_instance->max_file_size = 10 * 1024 * 1024; // 默认10MBg_instance->backup_count = 5;}}pthread_mutex_unlock(&g_instance_mutex);}return g_instance;
}// 初始化日志器
int logger_initialize(const LoggerConfig* config) {Logger* logger = logger_get_instance();if (!logger || !config) return -1;pthread_mutex_lock(&logger->mutex);// 关闭现有文件if (logger->log_file) {fclose(logger->log_file);logger->log_file = NULL;}// 释放旧文件名if (logger->filename) {free(logger->filename);logger->filename = NULL;}// 设置新配置logger->min_level = config->min_level;logger->max_file_size = config->max_file_size;logger->backup_count = config->backup_count;if (config->filename) {logger->filename = strdup(config->filename);if (!logger->filename) {pthread_mutex_unlock(&logger->mutex);return -1;}logger->log_file = fopen(config->filename, "a");if (!logger->log_file) {free(logger->filename);logger->filename = NULL;pthread_mutex_unlock(&logger->mutex);return -1;}}pthread_mutex_unlock(&logger->mutex);return 0;
}// 记录日志
void logger_log(LogLevel level, const char* format, ...) {Logger* logger = logger_get_instance();if (!logger || level < logger->min_level) return;pthread_mutex_lock(&logger->mutex);// 检查是否需要滚动日志if (logger->log_file && logger->filename) {struct stat st;if (fstat(fileno(logger->log_file), &st) == 0 && (size_t)st.st_size >= logger->max_file_size) {rotate_log_file(logger);}}if (logger->log_file) {// 写入日志头write_log_header(logger->log_file, level);// 写入日志内容va_list args;va_start(args, format);vfprintf(logger->log_file, format, args);va_end(args);// 确保换行fputc('\n', logger->log_file);fflush(logger->log_file);}// 调用钩子函数if (logger->hook) {va_list args;va_start(args, format);char buffer[1024];vsnprintf(buffer, sizeof(buffer), format, args);va_end(args);logger->hook(level, buffer, logger->hook_user_data);}pthread_mutex_unlock(&logger->mutex);
}// 注册日志钩子
void logger_register_hook(LogHook hook, void* user_data) {Logger* logger = logger_get_instance();if (!logger) return;pthread_mutex_lock(&logger->mutex);logger->hook = hook;logger->hook_user_data = user_data;pthread_mutex_unlock(&logger->mutex);
}// 清理日志器
void logger_cleanup(void) {pthread_mutex_lock(&g_instance_mutex);if (g_instance) {pthread_mutex_lock(&g_instance->mutex);if (g_instance->log_file) {fclose(g_instance->log_file);g_instance->log_file = NULL;}if (g_instance->filename) {free(g_instance->filename);g_instance->filename = NULL;}pthread_mutex_unlock(&g_instance->mutex);pthread_mutex_destroy(&g_instance->mutex);free(g_instance);g_instance = NULL;}pthread_mutex_unlock(&g_instance_mutex);
}// 内部函数:滚动日志文件
static int rotate_log_file(Logger* logger) {if (!logger->filename || !logger->log_file) return -1;fclose(logger->log_file);logger->log_file = NULL;// 删除最旧的备份文件if (logger->backup_count > 0) {char old_path[PATH_MAX];snprintf(old_path, sizeof(old_path), "%s.%d", logger->filename, logger->backup_count);remove(old_path);// 重命名其他备份文件for (int i = logger->backup_count - 1; i >= 1; i--) {char src_path[PATH_MAX];char dst_path[PATH_MAX];snprintf(src_path, sizeof(src_path), "%s.%d", logger->filename, i);snprintf(dst_path, sizeof(dst_path), "%s.%d", logger->filename, i + 1);rename(src_path, dst_path);}// 重命名当前日志文件为.1char first_backup[PATH_MAX];snprintf(first_backup, sizeof(first_backup), "%s.1", logger->filename);rename(logger->filename, first_backup);}// 重新打开日志文件logger->log_file = fopen(logger->filename, "a");return logger->log_file ? 0 : -1;
}// 内部函数:获取日志级别字符串
static const char* get_level_string(LogLevel level) {switch (level) {case LOG_LEVEL_DEBUG: return "DEBUG";case LOG_LEVEL_INFO: return "INFO";case LOG_LEVEL_WARNING: return "WARNING";case LOG_LEVEL_ERROR: return "ERROR";case LOG_LEVEL_CRITICAL: return "CRITICAL";default: return "UNKNOWN";}
}// 内部函数:写入日志头
static void write_log_header(FILE* file, LogLevel level) {time_t now = time(NULL);struct tm tm_now;localtime_r(&now, &tm_now);char time_buf[64];strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_now);fprintf(file, "[%s] %s - ", time_buf, get_level_string(level));
}
四、多线程测试用例
#include "logger.h"
#include <pthread.h>
#include <unistd.h>
#include <string.h>#define THREAD_COUNT 10
#define LOGS_PER_THREAD 20// 自定义日志钩子函数
void my_log_hook(LogLevel level, const char* message, void* user_data) {const char* prefix = (const char*)user_data;printf("[%s][Hook] %s: %s\n", prefix, get_level_string(level), message);
}// 线程函数
void* thread_func(void* arg) {int thread_num = *(int*)arg;char thread_name[16];snprintf(thread_name, sizeof(thread_name), "Thread%d", thread_num);for (int i = 0; i < LOGS_PER_THREAD; i++) {logger_log(LOG_LEVEL_INFO, "%s: Message %d", thread_name, i);usleep(10000); // 10ms}return NULL;
}int main() {// 初始化日志器LoggerConfig config = {.filename = "app.log",.min_level = LOG_LEVEL_DEBUG,.max_file_size = 1024, // 1KB for testing rotation.backup_count = 3};if (logger_initialize(&config) != 0) {fprintf(stderr, "Failed to initialize logger\n");return 1;}// 注册钩子函数logger_register_hook(my_log_hook, "MainHook");// 创建测试线程pthread_t threads[THREAD_COUNT];int thread_nums[THREAD_COUNT];for (int i = 0; i < THREAD_COUNT; i++) {thread_nums[i] = i;pthread_create(&threads[i], NULL, thread_func, &thread_nums[i]);}// 主线程也记录一些日志for (int i = 0; i < 5; i++) {logger_log(LOG_LEVEL_WARNING, "Main thread warning %d", i);usleep(50000); // 50ms}// 等待所有线程完成for (int i = 0; i < THREAD_COUNT; i++) {pthread_join(threads[i], NULL);}// 记录结束日志logger_log(LOG_LEVEL_INFO, "All threads completed");// 清理日志器logger_cleanup();return 0;
}
五、高级特性扩展
1. 支持多日志输出通道
typedef struct {FILE* file;LogLevel min_level;pthread_mutex_t mutex;
} LogChannel;struct Logger {LogChannel* channels;size_t channel_count;// 其他字段...
};int logger_add_channel(FILE* file, LogLevel min_level) {Logger* logger = logger_get_instance();if (!logger) return -1;pthread_mutex_lock(&logger->mutex);LogChannel* new_channels = realloc(logger->channels, (logger->channel_count + 1) * sizeof(LogChannel));if (!new_channels) {pthread_mutex_unlock(&logger->mutex);return -1;}logger->channels = new_channels;LogChannel* channel = &logger->channels[logger->channel_count++];channel->file = file;channel->min_level = min_level;pthread_mutex_init(&channel->mutex, NULL);pthread_mutex_unlock(&logger->mutex);return 0;
}
2. 支持日志格式化器注册
typedef void (*LogFormatter)(FILE* file, LogLevel level, const char* message);struct Logger {LogFormatter formatter;// 其他字段...
};void logger_set_formatter(LogFormatter formatter) {Logger* logger = logger_get_instance();if (!logger) return;pthread_mutex_lock(&logger->mutex);logger->formatter = formatter;pthread_mutex_unlock(&logger->mutex);
}// 在logger_log函数中使用:
if (logger->formatter) {logger->formatter(file, level, formatted_message);
} else {// 默认格式化
}
3. 支持异步日志写入
struct Logger {pthread_t async_thread;int async_running;pthread_cond_t async_cond;LogQueue* queue; // 需要实现一个线程安全的队列// 其他字段...
};void* async_log_thread(void* arg) {Logger* logger = (Logger*)arg;while (1) {pthread_mutex_lock(&logger->mutex);while (log_queue_empty(logger->queue) {if (!logger->async_running) {pthread_mutex_unlock(&logger->mutex);return NULL;}pthread_cond_wait(&logger->async_cond, &logger->mutex);}LogEntry entry = log_queue_pop(logger->queue);pthread_mutex_unlock(&logger->mutex);// 实际写入日志write_log_entry(&entry);}return NULL;
}int logger_start_async() {Logger* logger = logger_get_instance();if (!logger) return -1;pthread_mutex_lock(&logger->mutex);if (logger->async_running) {pthread_mutex_unlock(&logger->mutex);return 0;}logger->queue = log_queue_create();logger->async_running = 1;if (pthread_create(&logger->async_thread, NULL, async_log_thread, logger) != 0) {log_queue_destroy(logger->queue);logger->queue = NULL;logger->async_running = 0;pthread_mutex_unlock(&logger->mutex);return -1;}pthread_mutex_unlock(&logger->mutex);return 0;
}
六、性能优化建议
减少锁竞争:使用读写锁(pthread_rwlock_t)替代互斥锁实现细粒度锁策略批量写入:收集多条日志后一次性写入设置缓冲区大小阈值无锁队列:在异步日志模式下使用无锁队列减少线程间同步开销日志级别快速判断:
void logger_log(LogLevel level, const char* format, ...) {Logger* logger = logger_get_instance();if (!logger || level < logger->min_level) return;// ...}
热路径优化:
将关键路径上的函数声明为inline避免在关键路径上分配内存
七、错误处理与健壮性
1、文件写入错误处理:
if (fprintf(logger->log_file, "...") < 0) {// 处理写入错误logger->write_errors++;if (logger->write_errors > MAX_WRITE_ERRORS) {// 尝试恢复或切换到备用日志}
}
2、内存分配检查:
Logger* logger = malloc(sizeof(Logger));
if (!logger) {// 记录错误或使用备用方案return NULL;
}
3、递归调用保护:
void logger_log(...) {static __thread int in_logger = 0;if (in_logger) return;in_logger = 1;// ...in_logger = 0;
}
4、信号安全处理:
void signal_handler(int sig) {// 使用异步信号安全函数char msg[] = "Received signal X\n";msg[16] = '0' + sig;write(STDERR_FILENO, msg, sizeof(msg));}
八、跨平台考虑
1、路径分隔符:
#if defined(_WIN32)
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
2、线程实现:
#ifdef _WIN32
#include <windows.h>
#define thread_handle HANDLE
#else
#include <pthread.h>
#define thread_handle pthread_t
#endif
3、时间函数:
#ifdef _WIN32// Windows时间函数实现#else// POSIX时间函数实现#endif
九、线程安全单例日志系统的逻辑图
采用分层架构和模块化思想:
1、系统逻辑图
+-------------------------------------------------------+
| Application Layer |
+-------------------------------------------------------+
| 调用 logger_log() | 注册 hook | 设置 formatter |
+-------------------+------------+----------------------+↓
+-------------------------------------------------------+
| Logger Interface Layer |
+-------------------------------------------------------+
| get_instance() | 线程安全控制 | 多通道路由 |
| (双重检查锁定) (pthread_mutex) (channel选择器) |
+-------------------------------------------------------+↓
+-------------------------------------------------------+
| Core Functionality Layer |
+-------------------------------------------------------+
+------------------+ +------------------+ +-------------+
| 日志格式化模块 | 日志滚动模块 | 异步写入队列 |
| (时间戳/级别处理) | (大小检查/文件重命名)| (无锁队列) |
+------------------+ +------------------+ +-------------+↓
+-------------------------------------------------------+
| Output Layer |
+-------------------------------------------------------+
+-------------+ +-------------+ +-------------+ +-------+
| 文件输出通道 | 网络输出通道 | 控制台输出通道 | Hook系统 |
| (带缓冲IO) | (TCP/UDP) | (ANSI颜色) | (回调链)|
+-------------+ +-------------+ +-------------+ +-------+
2、关键模块交互流程图
3、扩展设计:插件式架构
- 动态加载输出插件
// plugin.h
typedef struct {const char* name;int (*init)(void* config);void (*write)(const char* msg);void (*cleanup)();
} LogPlugin;// 注册插件示例
void logger_register_plugin(LogPlugin* plugin) {pthread_mutex_lock(&g_plugin_mutex);g_plugins = realloc(g_plugins, (g_plugin_count+1)*sizeof(LogPlugin*));g_plugins[g_plugin_count++] = plugin;pthread_mutex_unlock(&g_plugin_mutex);
}
- 插件实现示例(Syslog输出)
// syslog_plugin.c
#include <syslog.h>static int syslog_init(void* config) {openlog("myapp", LOG_PID, LOG_USER);return 0;
}static void syslog_write(const char* msg) {syslog(LOG_INFO, "%s", msg);
}LogPlugin syslog_plugin = {.name = "syslog",.init = syslog_init,.write = syslog_write,.cleanup = closelog
};
4、性能优化对比表
优化策略 吞吐量提升 CPU占用降低 实现复杂度 适用场景
无锁队列 40-60% 30% 高 高频日志(>10k条/秒)
批量写入 25-35% 20% 中 磁盘IO瓶颈场景
读写锁 15-25% 10% 低 读多写少场景
内存池预分配 10-15% 5% 中 避免内存碎片
热路径内联 5-8% 2% 低 关键函数调用频繁
5、错误处理状态机
6、跨平台适配层设计
// platform.h
#ifdef _WIN32#define THREAD_HANDLE HANDLE#define MUTEX_TYPE CRITICAL_SECTION#define PATH_SEP '\\'
#else#define THREAD_HANDLE pthread_t#define MUTEX_TYPE pthread_mutex_t#define PATH_SEP '/'
#endif// 封装互斥锁操作
void mutex_lock(MUTEX_TYPE* m) {#ifdef _WIN32EnterCriticalSection(m);#elsepthread_mutex_lock(m);#endif
}
7、测试用例增强
1.并发压力测试
// test_concurrent.c
#define THREADS 100
#define LOGS_PER_THREAD 1000void* stress_test(void* arg) {for(int i=0; i<LOGS_PER_THREAD; i++){logger_log(LOG_DEBUG, "Thread %ld msg %d", (long)arg, i);}return NULL;
}int main() {LoggerConfig cfg = {.filename="stress.log", .max_file_size=GB(1)};logger_initialize(&cfg);pthread_t threads[THREADS];for(long i=0; i<THREADS; i++){pthread_create(&threads[i], NULL, stress_test, (void*)i);}// ...统计吞吐量...
}
2.故障注入测试
// 模拟磁盘满的情况
int mock_write(const char* msg) {static int fail_counter = 0;if(++fail_counter % 5 == 0) {errno = ENOSPC;return -1;}return real_write(msg);
}
十、总结
本文详细介绍了在Linux环境下使用C语言实现线程安全单例模式的完整方案,重点包括:
线程安全实现:通过互斥锁和双重检查锁定确保线程安全
复杂结构体封装:以日志器为例展示完整模块设计
动态注册机制:支持钩子函数和自定义格式化器
高级特性:日志滚动、异步写入、多通道支持等
性能优化:减少锁竞争、批量写入等技巧
最佳实践建议:
根据实际需求选择合适的单例实现方式
在性能关键场景考虑异步日志模式
实现完善的错误处理和恢复机制
设计良好的接口以支持未来扩展
在多线程环境中严格测试所有边界条件
这种单例模式实现方式适用于各种需要全局唯一实例的场景,如配置管理、设备驱动访问、服务代理等。