Linux 程序设计模式
单例模式
什么是单例模式
单例模式是一种创建型设计模式,它的核心目标是:
- 保证一个类在整个程序生命周期内,只能有一个实例对象;
- 提供一个全局的访问点(通常是静态成员函数),让外部可以获取这个唯一实例。
在 C++ 中,实现单例模式的核心约束有两点:
- 禁止外部创建 / 复制对象:将类的构造函数、拷贝构造函数、赋值运算符声明为
private,或直接用delete关键字禁用,防止外部通过任何方式创建多个实例; - 全局访问入口:通过一个静态成员函数(如
getInstance()),返回类的唯一实例(引用或指针)。
根据实例的创建时机,单例模式分为饿汉模式和懒汉模式,我们先重点讲解图片中提到的饿汉模式。
饿汉模式
饿汉模式的核心特点是:程序启动时(或编译阶段)就提前创建好唯一实例,并通过静态成员变量保存下来。
核心特性
- ✅ 优点:实现逻辑简单,且天然线程安全。 饿汉模式的实例是通过静态成员变量初始化的,在程序进入
main函数之前就已完成实例创建,此时还没有用户线程启动,不存在多线程竞争创建实例的问题,因此无需额外加锁就能保证线程安全。 - ❌ 缺点:资源可能浪费。 无论程序是否会用到这个实例,它都会在程序启动时被创建并占用资源,如果实例占用内存较大且实际未被使用,就会造成不必要的开销。
实现步骤
- 私有化构造与拷贝构造函数:禁止外部直接通过构造、拷贝创建新对象;
- 定义静态实例成员:在类内声明一个静态的类对象成员,用于保存唯一实例;
- 类外初始化静态实例:在类外对静态成员变量进行初始化,程序启动时自动创建实例;
- 提供全局访问函数:通过一个静态成员函数,返回这个静态实例的引用,让外部获取唯一对象;
- 通过实例访问类内功能:后续所有类内函数的调用,都通过这个唯一实例完成。
#include <iostream>
class Test
{
private:
// 1. 私有化构造函数,禁止外部直接创建对象
Test(int a, int b)
: _a(a), _b(b)
{
std::cout << "Test instance created" << std::endl;
}
// 2. 禁用拷贝构造和赋值运算符,防止通过拷贝创建新实例
Test(const Test&) = delete;
Test& operator=(const Test&) = delete;
// 3. 静态成员变量:保存唯一实例(饿汉模式的核心)
static Test _instance;
// 成员变量
int _a;
int _b;
public:
// 4. 全局访问点:静态成员函数,返回唯一实例的引用
static Test& getInstance()
{
return _instance;
}
// 类内功能函数,通过实例调用
void printInfo()
{
std::cout << "a:" << _a << ",b:" << _b << std::endl;
}
};
// 饿汉模式关键:类外初始化静态成员变量,程序启动时自动创建实例
Test Test::_instance(1, 2);
int main()
{
// 通过静态函数获取唯一实例
Test& t = Test::getInstance();
// 调用实例的功能函数
t.printInfo();
// 尝试创建新实例会直接报错(验证单例的唯一性)
// Test t2(3, 4); // 编译报错:构造函数私有
// Test t3 = t; // 编译报错:拷贝构造函数已被delete
return 0;
}

补充:饿汉模式的线程安全说明
图片中提到 “C++11 保证静态局部变量初始化是线程安全的,但此处用的是静态成员变量,同样线程安全”,这一点的原理是:
- 静态成员变量的初始化发生在程序加载阶段,在
main函数执行前完成,此时操作系统还没有调度任何用户线程,因此不存在多线程竞争的场景,是天然安全的; - 而懒汉模式(第一次调用
getInstance()时才创建实例)在多线程场景下可能出现 “多个线程同时判断实例为空,从而创建多个对象” 的问题,需要额外通过双重检查锁等方式保证线程安全。
懒汉模式
在第一次调用 getInstance() 时才创建实例。需要处理线程安全问题,避免多线程环境下创建多个实例。
非线程安全版本:仅适用于单线程。
双重检查锁定(DCLP)版本:C++11 前需要借助 std::atomic 和内存屏障,实现较复杂。
C++11 及之后推荐方法:利用静态局部变量的线程安全特性,简洁可靠。
实现方式:
1.构造函数和拷贝构造函数必须私有化
2.类内部成员必须包含静态实例化成员的指针变量,在类外初始化
3.第一次通过get_instance返回实例时判断指针为空,加锁,随后通过new开辟空间
4.通过静态实例化成员进行访问类内的函数
#include <iostream>
#include <mutex>
#include <pthread.h>
pthread_mutex_t mtx;
class test
{
private:
test(int a,int b)
:_a(a),_b(b)
{}
test(const test&) = delete;
public:
static test* get_instance()
{
if(_inst == nullptr)
{
pthread_mutex_lock(&mtx);//加锁
if(_inst == nullptr)
_inst = new test(1,2);
pthread_mutex_unlock(&mtx);
}
return _inst;
}
void print()
{
printf("a:%d,b:%d\n",_a,_b);
}
private:
int _a;
int _b;
static test* _inst;
};
test* test::_inst = nullptr;
int main()
{
pthread_mutex_init(&mtx, nullptr);
test::get_instance()->print();
return 0;
}

生产者消费者模型
什么是生产者消费者模型
生产者消费者模型是操作系统和多线程编程中的经典同步 / 互斥模型,在 Linux 多线程场景中尤为常见。它的核心目标是:解决生产者和消费者之间 “生产 / 消费速度不匹配” 的问题,同时实现两者的解耦与安全协作。
简单来说,模型描述了两类线程如何通过一个固定大小的共享缓冲区(仓库) 协调工作,避免数据竞争、死锁或资源浪费。
模型的三个核心要素:
| 角色 | 核心职责 | 阻塞规则 |
|---|---|---|
| 生产者线程 | 负责生成数据,将数据放入缓冲区 | 缓冲区满时,必须停止生产并阻塞,直到消费者取走数据腾出空间 |
| 消费者线程 | 负责从缓冲区取出数据并处理 | 缓冲区空时,必须停止消费并阻塞,直到生产者放入新数据 |
| 缓冲区(仓库) | 一段有限大小的共享内存(如队列、链表) | 解耦生产者和消费者,允许两者以不同速度运行,实现异步协作 |
快速记忆:

模型的核心优势
- 解耦:生产者和消费者不直接通信,所有交互都通过缓冲区完成,修改一方的逻辑不会直接影响另一方。
- 缓冲:平衡速度差异 —— 比如生产者生产快、消费慢时,缓冲区可暂存数据;反之则避免消费者空等。
- 可扩展:可独立新增 / 减少生产者或消费者线程,无需修改另一方的业务逻辑。
基于阻塞队列(BlockingQueue)的实现思路
阻塞队列(BlockingQueue)是实现生产者消费者模型的常用数据结构,它的特性天然适配模型的需求。
1. 阻塞队列与普通队列的区别
阻塞队列是一种特殊的线程安全队列,核心差异在于:
- 当队列为空时,消费者线程调用
pop/get操作会被阻塞,直到队列被放入新元素; - 当队列为满时,生产者线程调用
push/put操作会被阻塞,直到有元素被取出、队列腾出空间。
2. 阻塞队列如何实现模型逻辑
阻塞队列通过互斥锁 + 条件变量实现同步,核心流程如下:
生产者入队流程
- 加锁,访问队列;
- 判断队列是否已满:
- 若已满:释放锁,等待 “队列非满” 的条件变量,直到消费者取走数据;
- 若未满:将数据入队,唤醒等待的消费者线程,解锁。
消费者出队流程
- 加锁,访问队列;
- 判断队列是否为空:
- 若为空:释放锁,等待 “队列非空” 的条件变量,直到生产者放入数据;
- 若非空:取出队头数据,唤醒等待的生产者线程,解锁。
3. 关键实现细节
- 互斥锁:保护队列的读写操作,避免多个线程同时修改队列导致数据错乱;
- 条件变量:实现线程阻塞与唤醒,是模型同步的核心;
while循环判断:等待条件变量时,必须用while循环而非if,防止线程被 “虚假唤醒” 后错误操作队列。
#include <queue>
#include <mutex>
#include <condition_variable>
using namespace std;
template <typename T>
class BlockingQueue {
private:
queue<T> _q; // 底层队列
mutex _mtx; // 互斥锁
condition_variable _not_full; // 队列不满的条件变量(生产者等待)
condition_variable _not_empty;// 队列非空的条件变量(消费者等待)
size_t _max_size; // 缓冲区最大容量
public:
BlockingQueue(size_t max_size) : _max_size(max_size) {}
// 生产者:往队列中放入数据
void push(const T& data) {
unique_lock<mutex> lock(_mtx);
// while循环防止虚假唤醒
while (_q.size() >= _max_size) {
_not_full.wait(lock); // 队列满,生产者阻塞等待
}
_q.push(data);
_not_empty.notify_one(); // 唤醒等待的消费者
}
// 消费者:从队列中取出数据
T pop() {
unique_lock<mutex> lock(_mtx);
// while循环防止虚假唤醒
while (_q.empty()) {
_not_empty.wait(lock); // 队列空,消费者阻塞等待
}
T data = _q.front();
_q.pop();
_not_full.notify_one(); // 唤醒等待的生产者
return data;
}
};
模型的典型应用场景
生产者消费者模型在实际开发中非常常见,比如:
- 日志系统:多个业务线程作为生产者写入日志,后台线程作为消费者批量刷盘;
- 消息队列:生产者发送消息,消费者异步处理消息;
- 线程池:任务提交者作为生产者,工作线程作为消费者,通过任务队列解耦。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)