设计模式之单例模式在 C 语言中的应用(含 Linux 内核实例)

文章目录

        • 一、单例模式的定义与核心价值
        • [二、C 语言实现单例模式的核心思路](#二、C 语言实现单例模式的核心思路)
        • [三、 5 个实例](#三、 5 个实例)
          • [实例 1:基础懒汉式单例(日志管理器)](#实例 1:基础懒汉式单例(日志管理器))
          • [实例 2:饿汉式单例(配置管理器)](#实例 2:饿汉式单例(配置管理器))
          • [实例 3:内核风格单例(硬件控制器)](#实例 3:内核风格单例(硬件控制器))
          • [实例 4:单例模式 + 接口封装(缓存管理器)](#实例 4:单例模式 + 接口封装(缓存管理器))
          • [实例 5:线程安全的懒汉式单例(计数器服务)](#实例 5:线程安全的懒汉式单例(计数器服务))
        • [四、Linux 内核中的单例模式应用](#四、Linux 内核中的单例模式应用)
        • 五、实现注意事项
        • 六、补充说明
一、单例模式的定义与核心价值

单例模式(Singleton Pattern)是一种创建型设计模式,其核心是确保一个类(或结构体)在系统中仅有一个实例,并提供一个全局访问点。该模式通过限制对象的创建次数,避免重复分配资源,同时保证全局状态的一致性。

存在的意义:当系统中某些资源(如配置管理器、日志器、硬件控制器)需要被全局共享且仅需一份实例时(如多个模块同时操作同一硬件,重复初始化会导致冲突),直接创建多个实例会造成资源浪费、状态不一致或逻辑错误。单例模式通过强制单一实例,确保资源的唯一性和全局可访问性。

解决的问题

  • 全局共享资源被多次创建,导致内存 / 硬件资源浪费;
  • 多实例操作同一资源(如配置文件、硬件寄存器),引发状态冲突(如配置被重复覆盖);
  • 缺乏统一访问点,不同模块获取的实例不一致,导致逻辑混乱。
二、C 语言实现单例模式的核心思路

UML图例

  • 单例类 Singleton 私有构造函数(防止外部实例化);静态私有实例变量(存储唯一实例);静态公有方法(如getInstance()),提供全局访问点,确保返回唯一实例。
  • 客户端 Client 通过单例类的静态方法获取实例,调用实例的业务方法;不关心实例的创建细节,仅需保证使用的是同一个实例。

C 语言通过静态全局变量存储唯一实例 +封装创建函数控制实例生成实现单例,核心要点:

  1. 隐藏构造函数 :通过static修饰结构体定义或限制初始化函数的可见性,防止外部直接创建实例;
  2. 控制实例创建 :提供一个全局访问函数(如get_instance()),确保仅在首次调用时创建实例,后续调用直接返回已存在的实例;
  3. 线程安全保障:多线程环境下,通过互斥锁或原子操作防止并发创建导致的多实例问题;
  4. 资源释放 :提供销毁函数(如destroy_instance()),在程序退出时释放单例占用的资源(可选,内核场景中可能随系统退出自动释放)。
三、 5 个实例
实例 1:基础懒汉式单例(日志管理器)

"懒汉式" 指实例在首次使用时才创建,适合资源占用较大、可能不被使用的场景(如日志管理器)。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

// 1. 单例结构体:日志管理器(全局唯一)
typedef struct {
    FILE* log_file;       // 日志文件句柄
    const char* log_level;// 日志级别(INFO/ERROR)
} LogManager;

// 2. 静态全局变量存储唯一实例(隐藏在.c文件中,外部不可直接访问)
static LogManager* instance = NULL;
// 互斥锁:保证多线程下实例创建的原子性
static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;

// 3. 全局访问函数:获取单例实例(懒汉式创建)
LogManager* log_manager_get_instance(const char* filename, const char* level) {
    // 双重检查锁定(Double-Checked Locking):减少锁开销
    if (instance == NULL) {
        pthread_mutex_lock(&log_mutex); // 加锁
        if (instance == NULL) {         // 再次检查(防止并发场景下重复创建)
            instance = malloc(sizeof(LogManager));
            if (instance) {
                // 初始化单例:打开日志文件、设置级别
                instance->log_file = fopen(filename, "a");
                instance->log_level = level; // 简化处理:假设level为常量字符串
                printf("LogManager instance created. File: %s, Level: %s\n", filename, level);
            }
        }
        pthread_mutex_unlock(&log_mutex); // 解锁
    }
    return instance;
}

// 4. 日志写入接口(单例的核心功能)
void log_manager_write(LogManager* self, const char* msg) {
    if (!self || !self->log_file) return;
    fprintf(self->log_file, "[%s] %s\n", self->log_level, msg);
    fflush(self->log_file); // 立即刷新到文件
}

// 5. 单例销毁函数
void log_manager_destroy() {
    pthread_mutex_lock(&log_mutex);
    if (instance) {
        if (instance->log_file) {
            fclose(instance->log_file);
            printf("LogManager file closed\n");
        }
        free(instance);
        instance = NULL;
        printf("LogManager instance destroyed\n");
    }
    pthread_mutex_unlock(&log_mutex);
    pthread_mutex_destroy(&log_mutex); // 销毁互斥锁
}

// 模拟多线程场景:多个线程获取单例并写入日志
void* thread_func(void* arg) {
    const char* thread_name = (const char*)arg;
    LogManager* logger = log_manager_get_instance("app.log", "INFO");
    // 每个线程写入一条日志
    char msg[128];
    snprintf(msg, sizeof(msg), "Message from %s", thread_name);
    log_manager_write(logger, msg);
    return NULL;
}

int main() {
    // 1. 主线程获取单例并写入日志
    LogManager* main_logger = log_manager_get_instance("app.log", "INFO");
    log_manager_write(main_logger, "Main thread started");

    // 2. 启动3个线程,验证单例唯一性
    pthread_t t1, t2, t3;
    pthread_create(&t1, NULL, thread_func, "Thread 1");
    pthread_create(&t2, NULL, thread_func, "Thread 2");
    pthread_create(&t3, NULL, thread_func, "Thread 3");

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);

    // 3. 销毁单例
    log_manager_destroy();
    return 0;
}

上面的代码,运行结果如下

复制代码
LogManager instance created. File: app.log, Level: INFO
LogManager file closed
LogManager instance destroyed

其UML图如下,

即Client在首次调用log_manager_get_instance时创建实例,通过双重检查锁定(DCL)保证多线程安全,同时减少锁的开销;

,static保证全局仅存在一个LogManager实例,所有线程共享同一日志文件,避免多实例导致的文件操作冲突。

实例 2:饿汉式单例(配置管理器)

"饿汉式" 指实例在程序启动时(或模块加载时)就创建,适合资源占用小、必然会被使用的场景(如配置管理器)。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 1. 单例结构体:配置管理器(存储全局配置)
#define MAX_CONFIG_KEYS 10
typedef struct {
    char keys[MAX_CONFIG_KEYS][32];   // 配置键
    char values[MAX_CONFIG_KEYS][64]; // 配置值
    int count;                        // 已加载的配置数量
} ConfigManager;

// 2. 饿汉式:程序启动时直接初始化单例(静态变量在main前初始化)
static ConfigManager instance = {
    .count = 0 // 初始化为空配置
};

// 3. 全局访问函数:直接返回预初始化的实例
ConfigManager* config_manager_get_instance() {
    return &instance; // 无需创建,直接返回静态实例
}

// 4. 配置管理功能:添加配置
void config_manager_add(ConfigManager* self, const char* key, const char* value) {
    if (!self || self->count >= MAX_CONFIG_KEYS) return;
    strncpy(self->keys[self->count], key, sizeof(self->keys[0])-1);
    strncpy(self->values[self->count], value, sizeof(self->values[0])-1);
    self->count++;
    printf("Added config: %s=%s\n", key, value);
}

// 5. 配置管理功能:查询配置
const char* config_manager_get(ConfigManager* self, const char* key) {
    if (!self) return NULL;
    for (int i = 0; i < self->count; i++) {
        if (strcmp(self->keys[i], key) == 0) {
            return self->values[i];
        }
    }
    return "NOT_FOUND";
}

// 模拟不同模块使用配置管理器
void module_a_init() {
    ConfigManager* config = config_manager_get_instance();
    config_manager_add(config, "module_a_port", "8080");
    printf("Module A: port=%s\n", config_manager_get(config, "module_a_port"));
}

void module_b_init() {
    ConfigManager* config = config_manager_get_instance();
    config_manager_add(config, "module_b_timeout", "30");
    printf("Module B: timeout=%s\n", config_manager_get(config, "module_b_timeout"));
}

int main() {
    // 饿汉式单例在main前已初始化
    printf("Main: Initial config count=%d\n", config_manager_get_instance()->count);

    // 不同模块共享同一配置实例
    module_a_init();
    module_b_init();

    // 验证配置全局可见
    ConfigManager* main_config = config_manager_get_instance();
    printf("Main: module_a_port=%s\n", config_manager_get(main_config, "module_a_port"));
    printf("Main: module_b_timeout=%s\n", config_manager_get(main_config, "module_b_timeout"));

    return 0; // 静态实例随程序退出自动释放,无需手动销毁
}

上面的代码,运行结果如下

复制代码
Main: Initial config count=0
Added config: module_a_port=8080
Module A: port=8080
Added config: module_b_timeout=30
Module B: timeout=30
Main: module_a_port=8080
Main: module_b_timeout=30

其的UML图如下

即通过静态全局变量instance在程序启动时(main函数前)完成初始化,无需考虑线程安全(初始化在单线程阶段

实例 3:内核风格单例(硬件控制器)

模拟 Linux 内核中硬件控制器的单例设计(如 DMA 控制器、中断控制器),硬件资源唯一,需通过单例确保独占访问。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>
#include <stdbool.h>

// 1. 单例结构体:DMA控制器(硬件资源唯一)
typedef struct {
    uint32_t base_addr;    // 硬件基地址
    bool in_use;           // 是否正在使用
    pthread_mutex_t lock;  // 硬件访问锁
} DMAController;

// 2. 静态全局实例(内核中通常放在.bss段)
static DMAController* dma_instance = NULL;
static pthread_mutex_t dma_init_lock = PTHREAD_MUTEX_INITIALIZER;

// 3. 内核风格初始化函数:获取单例(模拟内核模块初始化)
int dma_controller_init(uint32_t base_addr) {
    pthread_mutex_lock(&dma_init_lock);
    if (dma_instance) {
        // 已初始化,直接返回成功
        pthread_mutex_unlock(&dma_init_lock);
        return 0;
    }

    // 首次初始化:分配并初始化实例
    dma_instance = malloc(sizeof(DMAController));
    if (!dma_instance) {
        pthread_mutex_unlock(&dma_init_lock);
        return -1; // 内存分配失败
    }

    // 初始化硬件控制器
    dma_instance->base_addr = base_addr;
    dma_instance->in_use = false;
    pthread_mutex_init(&dma_instance->lock, NULL);
    printf("DMA Controller initialized at 0x%08X\n", base_addr);

    pthread_mutex_unlock(&dma_init_lock);
    return 0;
}

// 4. 全局访问函数:获取控制器实例(内核中通常通过API访问)
DMAController* dma_controller_get() {
    return dma_instance; // 假设已初始化(内核中通常要求先init后使用)
}

// 5. 硬件操作接口:申请DMA通道(独占访问)
int dma_request_channel(DMAController* self) {
    if (!self) return -1;
    pthread_mutex_lock(&self->lock);
    if (self->in_use) {
        pthread_mutex_unlock(&self->lock);
        return -1; // 已被占用
    }
    self->in_use = true;
    printf("DMA channel requested (base: 0x%08X)\n", self->base_addr);
    pthread_mutex_unlock(&self->lock);
    return 0;
}

// 6. 硬件操作接口:释放DMA通道
void dma_release_channel(DMAController* self) {
    if (!self) return;
    pthread_mutex_lock(&self->lock);
    self->in_use = false;
    printf("DMA channel released (base: 0x%08X)\n", self->base_addr);
    pthread_mutex_unlock(&self->lock);
}

// 模拟内核模块使用DMA控制器
void kernel_module_a() {
    DMAController* dma = dma_controller_get();
    if (dma_request_channel(dma) == 0) {
        // 模拟使用DMA传输数据
        printf("Module A: DMA transfer completed\n");
        dma_release_channel(dma);
    } else {
        printf("Module A: Failed to get DMA channel\n");
    }
}

void kernel_module_b() {
    DMAController* dma = dma_controller_get();
    if (dma_request_channel(dma) == 0) {
        // 模拟使用DMA传输数据
        printf("Module B: DMA transfer completed\n");
        dma_release_channel(dma);
    } else {
        printf("Module B: Failed to get DMA channel\n");
    }
}

int main() {
    // 初始化DMA控制器(内核中通常在驱动加载时调用)
    if (dma_controller_init(0xFFFF0000) != 0) {
        printf("Failed to initialize DMA controller\n");
        return -1;
    }

    // 两个模块共享同一DMA控制器
    kernel_module_a();
    kernel_module_b(); // 此时通道已释放,可正常申请

    // 内核中通常不手动销毁(随系统 shutdown 释放)
    return 0;
}

以上代码运行结果如下

复制代码
DMA Controller initialized at 0xFFFF0000
DMA channel requested (base: 0xFFFF0000)
Module A: DMA transfer completed
DMA channel released (base: 0xFFFF0000)
DMA channel requested (base: 0xFFFF0000)
Module B: DMA transfer completed
DMA channel released (base: 0xFFFF0000)

其UML图例如下:

即模拟内核中硬件控制器的单例设计,硬件资源(如 DMA 控制器)物理唯一,通过单例确保只有一个实例管理硬件;同时提供dma_request_channeldma_release_channel接口控制硬件独占访问,避免多模块同时操作硬件导致冲突。

实例 4:单例模式 + 接口封装(缓存管理器)

将单例的实现细节隐藏在接口后,外部仅通过函数访问,不直接操作结构体,符合 C 语言 "信息隐藏" 的设计思想。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

// 1. 前向声明:隐藏结构体细节(仅在.c文件中定义)
typedef struct CacheManager CacheManager;

// 2. 对外接口声明(.h文件中可见)
CacheManager* cache_manager_get_instance();
void cache_manager_set(CacheManager* self, const char* key, const char* value);
const char* cache_manager_get(CacheManager* self, const char* key);
void cache_manager_destroy();

// 3. 结构体定义(仅在.c文件中可见,外部无法直接访问成员)
struct CacheManager {
    char keys[5][32];    // 缓存键
    char values[5][64];  // 缓存值
    int count;           // 缓存数量
    pthread_mutex_t lock; // 线程安全锁
};

// 4. 静态全局实例(隐藏实现)
static CacheManager* instance = NULL;
static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;

// 5. 接口实现:获取实例
CacheManager* cache_manager_get_instance() {
    if (!instance) {
        pthread_mutex_lock(&init_lock);
        if (!instance) {
            instance = malloc(sizeof(CacheManager));
            if (instance) {
                instance->count = 0;
                pthread_mutex_init(&instance->lock, NULL);
                printf("CacheManager instance created\n");
            }
        }
        pthread_mutex_unlock(&init_lock);
    }
    return instance;
}

// 6. 接口实现:设置缓存
void cache_manager_set(CacheManager* self, const char* key, const char* value) {
    if (!self || self->count >= 5) return;
    pthread_mutex_lock(&self->lock);
    // 检查是否已存在(简化处理:不覆盖,直接新增)
    for (int i = 0; i < self->count; i++) {
        if (strcmp(self->keys[i], key) == 0) {
            pthread_mutex_unlock(&self->lock);
            return;
        }
    }
    // 添加新缓存
    strncpy(self->keys[self->count], key, sizeof(self->keys[0])-1);
    strncpy(self->values[self->count], value, sizeof(self->values[0])-1);
    self->count++;
    printf("Cache set: %s=%s\n", key, value);
    pthread_mutex_unlock(&self->lock);
}

// 7. 接口实现:获取缓存
const char* cache_manager_get(CacheManager* self, const char* key) {
    if (!self) return NULL;
    pthread_mutex_lock(&self->lock);
    for (int i = 0; i < self->count; i++) {
        if (strcmp(self->keys[i], key) == 0) {
            pthread_mutex_unlock(&self->lock);
            return self->values[i];
        }
    }
    pthread_mutex_unlock(&self->lock);
    return "NOT_FOUND";
}

// 8. 接口实现:销毁实例
void cache_manager_destroy() {
    pthread_mutex_lock(&init_lock);
    if (instance) {
        pthread_mutex_destroy(&instance->lock);
        free(instance);
        instance = NULL;
        printf("CacheManager instance destroyed\n");
    }
    pthread_mutex_unlock(&init_lock);
    pthread_mutex_destroy(&init_lock);
}

// 客户端使用:仅通过接口访问,不依赖结构体实现
int main() {
    CacheManager* cache = cache_manager_get_instance();

    // 设置缓存
    cache_manager_set(cache, "user", "admin");
    cache_manager_set(cache, "role", "root");

    // 获取缓存
    printf("Get user: %s\n", cache_manager_get(cache, "user"));
    printf("Get role: %s\n", cache_manager_get(cache, "role"));

    // 销毁实例
    cache_manager_destroy();
    return 0;
}

以上代码运行结果

复制代码
CacheManager instance created
Cache set: user=admin
Cache set: role=root
Get user: admin
Get role: root
CacheManager instance destroyed

其的UML图为

即通过前向声明typedef struct CacheManager CacheManager隐藏结构体细节,外部仅能通过接口函数操作单例,符合 C 语言的模块化设计; 单例的创建、销毁和功能实现被封装在接口后,即使内部实现修改(如缓存大小调整),客户端代码也无需变动。

实例 5:线程安全的懒汉式单例(计数器服务)

使用 C11 的stdatomic库实现无锁线程安全,适合高性能场景(如全局计数器)。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <pthread.h>

// 1. 单例结构体:全局计数器服务
typedef struct {
    atomic_uint count; // 原子计数器(线程安全)
} CounterService;

// 2. 静态全局实例指针(原子类型,确保并发访问安全)
static _Atomic(CounterService*) instance = NULL;

// 3. 全局访问函数:无锁线程安全(基于C11 atomic)
CounterService* counter_service_get_instance() {
    // 首次读取实例(弱原子操作,开销低)
    CounterService* tmp = atomic_load_explicit(&instance, memory_order_acquire);
    if (!tmp) {
        // 临时分配实例(仅在首次创建时执行)
        CounterService* new_instance = malloc(sizeof(CounterService));
        if (new_instance) {
            atomic_init(&new_instance->count, 0); // 初始化计数器为0
        }
        // 原子比较交换:若instance仍为NULL,则设置为new_instance
        if (!atomic_compare_exchange_strong_explicit(
            &instance, &tmp, new_instance,
            memory_order_release, memory_order_acquire)) {
            // 交换失败:说明其他线程已创建实例,释放当前new_instance
            free(new_instance);
            tmp = atomic_load_explicit(&instance, memory_order_acquire);
        }
        printf("CounterService instance created\n");
    }
    return tmp;
}

// 4. 计数器功能:自增并返回新值
unsigned int counter_service_increment(CounterService* self) {
    if (!self) return 0;
    return atomic_fetch_add(&self->count, 1) + 1; // 原子自增
}

// 5. 销毁函数
void counter_service_destroy() {
    CounterService* tmp = atomic_load_explicit(&instance, memory_order_acquire);
    if (tmp) {
        atomic_store_explicit(&instance, NULL, memory_order_release);
        free(tmp);
        printf("CounterService instance destroyed\n");
    }
}

// 模拟多线程并发计数
#define THREAD_COUNT 5
void* thread_func(void* arg) {
    CounterService* counter = counter_service_get_instance();
    for (int i = 0; i < 1000; i++) {
        counter_service_increment(counter);
    }
    return NULL;
}

int main() {
    pthread_t threads[THREAD_COUNT];
    // 启动多个线程并发计数
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_create(&threads[i], NULL, thread_func, NULL);
    }
    // 等待所有线程结束
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_join(threads[i], NULL);
    }
    // 验证计数结果(应为5*1000=5000)
    CounterService* counter = counter_service_get_instance();
    printf("Final count: %u\n", atomic_load(&counter->count));

    counter_service_destroy();
    return 0;
}

以上的代码运行结果为如下

复制代码
CounterService instance created
Final count: 4000
CounterService instance destroyed

执行结果出现4000而非预期的5000,原因可能是:在counter_service_get_instance函数中,当多个线程同时进入if (!tmp)分支时,虽然atomic_compare_exchange_strong_explicit能保证最终只有一个线程成功创建实例,但其他线程在free(new_instance)后,会再次通过tmp = atomic_load_explicit获取实例。然而,这里的tmp在compare_exchange失败后已经被更新为 "其他线程创建的实例地址"(compare_exchange的第二个参数是输出型,会返回当前instance的值),因此后续的tmp = atomic_load_explicit可能是冗余的,但通常不会导致错误

修改代码

复制代码
 // 原子比较交换:若instance仍为NULL,则设置为new_instance
        if (!atomic_compare_exchange_strong_explicit(
            &instance, &tmp, new_instance,
            memory_order_release, memory_order_acquire)) {
            // 交换失败:说明其他线程已创建实例,释放当前new_instance
            free(new_instance);
            tmp = atomic_load_explicit(&instance, memory_order_acquire);
        }

==>
        // 原子比较交换:若instance仍为NULL,则设置为new_instance
        if (!atomic_compare_exchange_strong_explicit(
            &instance, &tmp, new_instance,
            memory_order_release, memory_order_acquire)) {
            // 交换失败:说明其他线程已创建实例,释放当前new_instance
            free(new_instance);
		}
        else
         {   tmp = new_instance;//atomic_load_explicit(&instance, memory_order_acquire);
        }

这样重新编译后,执行结果为

复制代码
CounterService instance created
Final count: 5000
CounterService instance destroye

即使用 C11 的_Atomic和原子操作(atomic_loadatomic_compare_exchange_strong)实现无锁线程安全,避免互斥锁的性能开销;适合高性能场景(如全局计数器),多个线程可并发调用counter_service_increment,通过原子操作保证计数准确性。

四、Linux 内核中的单例模式应用
  1. 进程 ID(PID)分配器 :内核中pid_namespace的 PID 分配器是典型单例,每个命名空间内仅有一个 PID 管理器(struct pid_namespace),负责分配和回收 PID,确保同一命名空间内 PID 的唯一性。其全局访问点为task_active_pid_ns(current),返回当前进程所在的 PID 命名空间单例。
  2. 页分配器(Buddy System) :内核的伙伴系统是管理物理内存页的单例组件,系统启动时初始化(buddy_init),全局仅有一个实例负责内存页的分配(__alloc_pages)和释放(__free_pages)。通过get_page_from_freelist等函数全局访问,确保物理内存管理的唯一性。
  3. 中断控制器(IRQ Controller) :对于特定硬件架构(如 ARM GIC),中断控制器是物理唯一的硬件资源,内核通过struct irq_chipirq_desc数组实现单例管理,全局仅有一套中断处理机制,通过request_irqfree_irq等接口访问,避免多实例导致的中断路由冲突。
  4. 系统时间管理器(jiffies) :内核中的jiffies(节拍计数器)是全局唯一的时间戳,由定时器中断驱动自增,所有模块通过get_jiffies_64获取当前时间,本质是一个只读单例。其初始化在系统启动时完成,全程唯一且不可修改(除定时器中断)。
  5. VFS 超级块管理器 :虚拟文件系统(VFS)的超级块链表(super_blocks)是单例管理结构,全局仅有一个链表记录所有已挂载文件系统的超级块,通过get_superput_super等函数访问,确保文件系统挂载状态的全局一致性。
五、实现注意事项
  1. 线程安全是核心
    • 懒汉式单例必须处理多线程并发创建问题,推荐使用 "双重检查锁定(DCL)+ 互斥锁" 或 C11 原子操作;
    • 饿汉式单例在单线程初始化阶段(main前)创建,天然线程安全,但需确保初始化逻辑无副作用(如依赖其他未初始化的全局变量)。
  2. 资源释放需谨慎
    • 应用程序中,单例应提供destroy函数,在程序退出前释放资源(如文件句柄、内存),避免资源泄漏;
    • 内核模块中,单例通常随模块卸载(module_exit)释放,但需确保此时无其他模块正在使用单例(可通过引用计数控制)。
  3. 禁止复制与赋值
    • C 语言中需通过static和封装接口防止单例被复制(如不提供拷贝函数,将结构体定义为static),避免通过指针赋值创建新实例。
  4. 懒汉式 vs 饿汉式的选择
    • 懒汉式:适合资源密集型组件(如大缓存),延迟创建节省资源,但需处理线程安全;
    • 饿汉式:适合轻量、必用组件(如配置管理器),初始化简单且线程安全,但提前占用资源。
  5. 避免过度使用
    • 单例本质是 "全局变量的封装",过度使用会导致代码耦合紧密、测试困难(全局状态难以隔离);仅在资源确实唯一且需全局共享时使用(如硬件控制器、全局计数器)。
六、补充说明
  • 单例模式与全局变量的区别:

    单例通过接口控制实例创建,隐藏实现细节,支持延迟初始化和线程安全,比直接使用全局变量更可控。全局变量在程序启动时初始化,无法延迟,且容易被意外修改。

  • 单例的变体:

    • 枚举单例:C 语言中可通过枚举 + 全局函数模拟(适合状态简单的单例),但功能有限;
    • 嵌套单例:在单例中包含其他单例(如 "配置管理器" 包含 "日志管理器"),形成单例依赖树,需注意初始化顺序。
  • 测试与单例:

    单例的全局状态会导致单元测试困难(测试用例间状态污染),可通过 "依赖注入" 优化:在测试环境中替换单例实例为 mock 对象,避免依赖真实全局状态。

单例模式在 C 语言中(尤其是 Linux 内核)是保证资源唯一性的核心手段,通过合理使用可避免资源冲突、简化全局状态管理,但需严格控制线程安全和使用场景,防止过度设计导致的代码僵化。

|---------------------|
| 点击下面关注,获取最新最全分享,不迷路 |

相关推荐
__万波__1 小时前
二十三种设计模式(一)--单例模式
java·单例模式·设计模式
zore_c2 小时前
【C语言】数据在内存中的存储(超详解)
c语言·开发语言·数据结构·经验分享·笔记
达不溜先生 ୧⍢⃝୨2 小时前
循环赛日程表问题
c语言·算法·递归·分治·循环赛日程表·动态二维数组
不知所云,2 小时前
2.windows c/c++ 编译器安装, mingw和clang
c语言·c++·windows·mingw·clang·c编译器
LCG米2 小时前
工业自动化嵌入式开发实战:基于ARM7与μC/OS-II的焊接机控制系统设计与实现
运维·c语言·自动化
Yue丶越3 小时前
【C语言】内存函数
c语言·开发语言
apocelipes3 小时前
Linux的binfmt_misc机制
linux·c语言·c++·python·golang·linux编程·开发工具和环境
渡我白衣3 小时前
哈希的暴力美学——std::unordered_map 的底层风暴、扩容黑盒与哈希冲突终极博弈
java·c语言·c++·人工智能·深度学习·算法·哈希算法
LCG米3 小时前
工业自动化案例解析:基于ARM7与μC/OS-II的焊接机控制系统设计
运维·c语言·自动化