设计模式之单例模式在 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 内核)是保证资源唯一性的核心手段,通过合理使用可避免资源冲突、简化全局状态管理,但需严格控制线程安全和使用场景,防止过度设计导致的代码僵化。

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

相关推荐
程序员Rock7 小时前
上位机开发-MODBUS面试常见问题
c语言·c++·面试·职场和发展·上位机
张小姐的猫9 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充11 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充12 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充12 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充12 小时前
2.面向对象的特性
设计模式
杨充12 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充12 小时前
8.反模式与坏味道
设计模式·开源·代码规范
blevoice12 小时前
抖抖燃脂机单 AC6966B 主控调试踩坑:Type-C 整机供电喇叭无声排障
c语言·开发语言·单片机
杨充12 小时前
3.接口vs抽象类比较
设计模式