【知识分享】C语言中的设计模式——单例模式

本文介绍单例模式在C语言中的应用,区分懒汉式和饿汉式,探讨线程锁解决重入问题,并强调其在驱动开发中的内存节省优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

    在23种设计模式中,单例模式属于创建型模式,在面向对象设计里是最简单的一种设计模式。

名词释义

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

C语言应用

    说白了就是有创建一个唯一变量实体,全局共享。

例子

    这个模式在单片机底层驱动会用得比较多,比如ST官方的HAL库,基本就是一个外设定义一个实体。下面我们就以串口通信为例,来看下这个模式有什么特点。
    根据创建实体的位置,这里分为懒汉式和饿汉式两种。
懒汉式
    顾名思义,很懒,只有在使用的时候才创建实体。建议在存在内存管理的系统中使用。

#include <stdint.h>
#include <stdlib.h>

/* 串口通信缓存大小 */
#define UART_BUFF_SIZE		255

/* 串口通信结构块 */
struct tagUartCB
{
	uint8_t Buff[UART_BUFF_SIZE];
	uint8_t Size;
};

/* 获取实体 */
struct tagUartCB* GetUartCB(void)
{
	static struct tagUartCB* uart_cb = NULL;
	
	if (NULL == uart_cb)
	{
		/* --此处可能被重入-- */
		uart_cb = malloc(sizeof(struct tagUartCB));
	}
	
	return uart_cb ;
}

饿汉式
    就如名字所说,饿汉,巴不得马上就可以使用,所以程序开始运行时即已创建实体。模块内部创建实体,对外只提供一个调用函数。

#include <stdint.h>
#include <stdlib.h>
/* 串口通信缓存大小 */
#define UART_BUFF_SIZE		255

/* 串口通信结构块 */
struct tagUartCB
{
	uint8_t Buff[UART_BUFF_SIZE];
	uint8_t Size;
};

/* 唯一实体 */
static struct tagUartCB UartCB;

/* 获取实体 */
struct tagUartCB* GetUartCB(void)
{
	return UartCB;
}

线程锁
    在使用懒汉式的时候,会存在一个问题,就是在多线程调用时,可能会导致重入,从而导致创建了多个实体,为了解决这个问题,这里可以加个线程锁。

#include <stdint.h>
#include <stdlib.h>

/* 串口通信缓存大小 */
#define UART_BUFF_SIZE		255

/* 串口通信结构块 */
struct tagUartCB
{
	uint8_t Buff[UART_BUFF_SIZE];
	uint8_t Size;
};

/* 获取实体 */
struct tagUartCB* GetUartCB(void)
{
	static struct tagUartCB* uart_cb = NULL;
	/* 禁止任务调度 */
	xTaskResumeAll();
	
	if (NULL == uart_cb)
	{
		uart_cb = malloc(sizeof(struct tagUartCB));
	}
	
	/* 启用任务调度 */
	vTaskSuspendAll();
	
	return uart_cb ;
}

    因为一直调用禁止任务调度会影响其他任务的运行时效性,所以为了不影响正常使用的性能,上面代码可以改也如下方式。

/* 获取实体 */
struct tagUartCB* GetUartCB(void)
{
	static struct tagUartCB* uart_cb = NULL;
	/* 已创建了实体就不禁止任务调度 */
	if (NULL == uart_cb)
	{
		/* 禁止任务调度 */
		xTaskResumeAll();
		
		if (NULL == uart_cb)
		{
			uart_cb = malloc(sizeof(struct tagUartCB));
		}
		
		/* 启用任务调度 */
		vTaskSuspendAll();
	}
	
	return uart_cb ;
}

适用范围

    一般在驱动中最为常见,比如现在有一个板子,上面有两个485通信,如果此时要做串口的通信缓存,那只要做两套缓存即可,即使逻辑层面上,两个485可以跟多个设备进行通信,但每个485同一时刻只能跟一个设备通信,即同一时刻只需要一套缓存,那么此时多个设备的通信缓存是可以共享的。这时候使用单例模式可以大量减少内存的消耗。

优势

  1. 节省Ram空间
  2. 资源共享,不需要考虑同步问题

劣势

  1. 共用全局资源,多线程使用时需要考虑重入的风险。
### 关于ArcGIS License Server无法启动的解决方案 当遇到ArcGIS License Server无法启动的情况,可以从以下几个方面排查并解决问题: #### 1. **检查网络配置** 确保License Server所在的计算机能够被其他客户端正常访问。如果是在局域网环境中部署了ArcGIS Server Local,则需要确认该环境下的网络设置是否允许远程连接AO组件[^1]。 #### 2. **验证服务状态** 检查ArcGIS Server Object Manager (SOM) 的运行情况。通常情况下,在Host SOM机器上需将此服务更改为由本地系统账户登录,并重启相关服务来恢复其正常工作流程[^2]。 #### 3. **审查日志文件** 查看ArcGIS License Manager的日志记录,寻找任何可能指示错误原因的信息。这些日志可以帮助识别具体是什么阻止了许可服务器的成功初始化。 #### 4. **权限问题** 确认用于启动ArcGIS License Server的服务账号具有足够的权限执行所需操作。这包括但不限于读取/写入特定目录的权利以及与其他必要进程通信的能力。 #### 5. **软件版本兼容性** 保证所使用的ArcGIS产品及其依赖项之间存在良好的版本匹配度。不一致可能会导致意外行为完全失败激活license server的功能。 #### 示例代码片段:修改服务登录身份 以下是更改Windows服务登录凭据的一个简单PowerShell脚本例子: ```powershell $serviceName = "ArcGISServerObjectManager" $newUsername = ".\LocalSystemUser" # 替换为实际用户名 $newPassword = ConvertTo-SecureString "" -AsPlainText -Force Set-Service -Name $serviceName -StartupType Automatic New-ServiceCredential -ServiceName $serviceName -Account $newUsername -Password $newPassword Restart-Service -Name $serviceName ``` 上述脚本仅作为示范用途,请依据实际情况调整参数值后再实施。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知识噬元兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值