
💡Yupureki:个人主页
✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》《MySQL数据库》
🌸Yupureki🌸的简介:

目录
[1. 单例模式](#1. 单例模式)
[1.1 什么是单例模式](#1.1 什么是单例模式)
[1.2 饿汉模式](#1.2 饿汉模式)
[1.3 懒汉模式](#1.3 懒汉模式)
[2. 责任链模式与消息队列](#2. 责任链模式与消息队列)
[2.1 什么是责任链模式](#2.1 什么是责任链模式)
[2.2 消息队列](#2.2 消息队列)
[2.2.1 创建/获取队列](#2.2.1 创建/获取队列)
[2.2.2 发送消息](#2.2.2 发送消息)
[2.2.3 接收消息](#2.2.3 接收消息)
[2.2.4 控制操作](#2.2.4 控制操作)
[2.2.5 示例](#2.2.5 示例)
[2.2.6 C++封装接口](#2.2.6 C++封装接口)
[2.3 基于责任链模式的消息队列](#2.3 基于责任链模式的消息队列)
[2.3.1 设计思路](#2.3.1 设计思路)
[2.3.2 责任链设计](#2.3.2 责任链设计)
[2.3.3 客户端和服务端设计](#2.3.3 客户端和服务端设计)
[3. 建造者模式和System V 信号量](#3. 建造者模式和System V 信号量)
[3.1 什么是建造者模式](#3.1 什么是建造者模式)
[3.2 System V 信号量](#3.2 System V 信号量)
[3.2.1 创建/获取信号量集](#3.2.1 创建/获取信号量集)
[3.2.2 控制信号量](#3.2.2 控制信号量)
[3.2.3 操作信号量(P/V 操作)](#3.2.3 操作信号量(P/V 操作))
[3.2.4 示例](#3.2.4 示例)
[3.2.5 C++封装接口](#3.2.5 C++封装接口)
[3.3 基于建造者模式的信号量](#3.3 基于建造者模式的信号量)
[3.3.1 设计思路](#3.3.1 设计思路)
[3.3.2 建造者设计](#3.3.2 建造者设计)
[3.3.3 main函数设计](#3.3.3 main函数设计)
[4. 生产者消费者模型](#4. 生产者消费者模型)
[4.1 什么是生产者消费者模型](#4.1 什么是生产者消费者模型)
[4.2 基于BlockingQueue的生产者消费者模型](#4.2 基于BlockingQueue的生产者消费者模型)
[4.2.1 设计思路](#4.2.1 设计思路)
[4.2.1 模型设计](#4.2.1 模型设计)
1. 单例模式
1.1 什么是单例模式
单例模式是一种创建型设计模式,它保证一个类在程序生命周期内只有一个实例,并提供一个全局访问点来获取该实例。在 C++ 中实现单例时,通常需要:
-
将构造函数、拷贝构造函数、赋值运算符声明为
private或delete,防止外部创建多个对象。 -
通过一个静态成员函数(如
getInstance())返回唯一实例的引用或指针。
根据实例创建时机的不同,单例模式分为饿汉模式 和懒汉模式。
1.2 饿汉模式
在程序启动时(或编译期)就创建唯一实例 ,通过静态成员变量保存。
- 优点:简单、线程安全(C++11 保证静态局部变量初始化是线程安全的,但此处用的是静态成员变量,其在程序启动时初始化,同样线程安全)。
- 缺点:无论是否使用都会创建实例,可能造成资源浪费。
实现方式:
- 构造函数和拷贝构造函数必须私有化
- 类内部成员必须包含静态实例化成员,在类外初始化
- 需要提供返回静态实例化成员的函数
- 通过静态实例化成员进行访问类内的函数
cpp
#include <iostream>
class test
{
private:
test(int a,int b)
:_a(a),_b(b)
{}
test(const test&) = delete;
public:
static test& get_instance()//返回静态实例化成员
{
return _inst;
}
void print()
{
printf("a:%d,b:%d\n",_a,_b);
}
private:
int _a;
int _b;
static test _inst;
};
test test::_inst(1,2);//类外初始化
int main()
{
test::get_instance().print();
return 0;
}

1.3 懒汉模式
在第一次调用 getInstance() 时才创建实例。需要处理线程安全问题,避免多线程环境下创建多个实例。
-
非线程安全版本:仅适用于单线程。
-
双重检查锁定(DCLP)版本:C++11 前需要借助
std::atomic和内存屏障,实现较复杂。 -
C++11 及之后推荐方法:利用静态局部变量的线程安全特性,简洁可靠。
实现方式:
- 构造函数和拷贝构造函数必须私有化
- 类内部成员必须包含静态实例化 成员的指针变量,在类外初始化
- 第一次通过get_instance返回实例时判断指针为空,加锁,随后通过new开辟空间
- 通过静态实例化成员进行访问类内的函数
cpp
#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;
}
2. 责任链模式与消息队列
2.1 什么是责任链模式
责任链模式是一种行为设计模式,它允许多个对象都有机会处理请求,从而避免请求的发送者与接收者之间的耦合。这些对象被串成一条链,请求沿着链传递,直到有一个对象处理它为止。

在责任链中,每个处理者会先对自身进行检查,如果有处理的权限,就会进行相应的处理,随后传给下一人。如果没有权限,就会直接交给下一人。
这种高解耦合,逻辑清晰的处理方式提高了代码的灵活性和可维护性。我们可以自由设置每个处理者的处理方式和权限,也可以在责任链中新增处理者
2.2 消息队列
在Linux系统中,消息队列是进程间通信(IPC)的重要方式之一,允许进程以消息 为单位交换数据,每条消息可以带有类型 或优先级 ,便于灵活处理。Linux提供了两套主流的消息队列接口:POSIX消息队列 和System V消息队列 。这里我们使用System V消息队列
2.2.1 创建/获取队列
cpp
int msgget(key_t key, int msgflg);
-
key:通常使用ftok()生成。 -
msgflg:权限标志,可组合IPC_CREAT等。 -
返回值:成功返回消息队列标识符,失败返回
-1。
2.2.2 发送消息
cpp
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
-
msgp:指向消息结构的指针,消息结构必须包含一个long mtype成员,后跟数据。cppstruct msgbuf { long mtype; // 消息类型(正整数) char mtext[1]; // 实际数据 }; -
msgsz:mtext的大小(不包含mtype)。 -
msgflg:IPC_NOWAIT表示非阻塞。
2.2.3 接收消息
cpp
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
-
msgtyp:选择接收的消息类型。等于0则接收队列中第一条消息;大于0接收类型等于该值的消息;小于0接收类型小于等于其绝对值的消息。 -
msgflg:IPC_NOWAIT、MSG_NOERROR(截断过长消息)。
2.2.4 控制操作
cpp
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd:IPC_RMID删除队列,IPC_STAT获取状态等。
2.2.5 示例
cpp
示例:发送和接收
c
// 发送端
key_t key = ftok("/tmp", 'A');
int msqid = msgget(key, IPC_CREAT | 0666);
struct msgbuf {
long mtype;
char mtext[100];
} msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello");
msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0);
// 接收端
int msqid = msgget(key, 0666);
struct msgbuf msg;
msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
printf("Received: %s\n", msg.mtext);
msgctl(msqid, IPC_RMID, NULL);
2.2.6 C++封装接口
cpp
#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>
#define MSG_CREATE IPC_CREAT | IPC_EXCL | 0666//创建消息队列
#define MSG_GET IPC_CREAT//获取消息队列
#define DEFAULT_PATHNAME "/tmp"
#define DEFAULT_ID 'A'
#define DEFAULT_SIZE 1024
#define CLIENT_TYPE 1
#define SERVER_TYPE 2
class MsgQueue//消息队列接口
{
private:
struct msgbuf {
long mtype;
char mtext[DEFAULT_SIZE];
};
public:
MsgQueue(std::string pathname = DEFAULT_PATHNAME,int id = DEFAULT_ID)
{
_key = ftok(pathname.c_str(),id);
if(_key < 0)
{
std::cout<<"ftok error"<<std::endl;
return;
}
std::cout<<"key:"<<_key<<std::endl;
}
void Create(int flag)//创建/获取消息队列
{
_msqid = msgget(_key,flag);
if(_msqid < 0)
{
std::cout<<"msgget error"<<std::endl;
return;
}
std::cout<<"msqid:"<<_msqid<<std::endl;
}
void Send(int type,const std::string& buffer)//发送数据
{
msgbuf tmp;
tmp.mtype = type;
buffer.copy(tmp.mtext,sizeof(tmp),0);
int n = msgsnd(_msqid,&tmp,sizeof(tmp.mtext),0);
if(n < 0)
{
std::cout<<"msgsnd error"<<std::endl;
return;
}
}
std::string Recv(int type)//接收数据
{
msgbuf tmp;
memset(&tmp,0,sizeof(tmp));
int n = msgrcv(_msqid,&tmp,sizeof(tmp.mtext),type,0);
if(n >= 0)
{
tmp.mtext[n] = '\0';
return tmp.mtext;
}
return "NONE";
}
~MsgQueue()
{
if(_msqid)
msgctl(_msqid,IPC_RMID,nullptr);
}
private:
int _msqid = -1;
key_t _key;
};
class Server : public MsgQueue
{
public:
Server()
{
Create(MSG_CREATE);//Server端创建并获取消息队列
}
private:
};
class Client : public MsgQueue
{
public:
Client()
{
Create(MSG_GET);//Client只获取消息队列
}
private:
};
2.3 基于责任链模式的消息队列
2.3.1 设计思路
新需求:
- client发送给server的输入内容,拼接上时间,进程pid信息
- server收到的内容持久化保存到文件中
- 文件的内容如果过大,要进行切片保存并在指定的目录下打包保存,命令自定义
Server端处理文件时,在责任链上传递
- HandlerTextFormat:对内容拼接上时间,进程pid信息
- HandlerTextSaveFile:将内容保存到文件中
- HandlerTextBackUp:文件过大时,进行备份
- 这些类具有相同点:处理和传递,因此我们采用继承加多态的方式,设计HandlerText(模板类),其余的类继承HandlerText类,这样有相同的构造和接口
流程:HandlerTextEntry(入口)->HandlerTextFormat->HandlerTextSaveFile->HandlerTextBackUp
2.3.2 责任链设计
HandlerText:模板类
Execute:责任链中处理者的自定义处理方式
_is_enable:处理的权限,如果没有直接传递给下一个
_next:指向下一个处理者的指针
cpp
class HandlerText
{
public:
virtual void Execute(std::string& str) = 0;
void Set_next(std::shared_ptr<HandlerText>& next)
{
_next = next;
}
void Enable()
{
_is_enable = true;
}
void Unable()
{
_is_enable = false;
}
protected:
bool _is_enable = true;
std::shared_ptr<HandlerText> _next;
};
HandlerTextFormat:对内容拼接上时间,进程pid信息
cpp
class HandlerTextFormat : public HandlerText
{
public:
void Execute(std::string& str)override
{
if(_is_enable)
{
str = std::to_string(time(nullptr)) + "-" + std::to_string(getpid()) + "-" + str + "\n";
std::cout<<"Format:" <<str<<std::endl;
}
if(_next)
{
_next->Execute(str);
}
else
{
std::cout<<"end chain"<<std::endl;
}
}
private:
};
HandlerTextSaveFile:将内容保存到文件中
cpp
#define DEFAULT_PATH "./tmp/"
#define DEFAULT_FILENAME "log.txt"
class HandlerTextSaveFile : public HandlerText
{
public:
HandlerTextSaveFile(std::string path = DEFAULT_PATH,std::string filename = DEFAULT_FILENAME)
:_path(path),_filename(filename)
{
std::string _path_file = _path + _filename;
_fd = open(_path_file.c_str(),O_WRONLY | O_CREAT | O_APPEND,0644);
if(_fd < 0)
{
std::cout<<"open file error"<<std::endl;
Unable();
return;
}
}
void Execute(std::string& str)override
{
if(_is_enable)
{
int n = write(_fd,str.c_str(),str.size());
if(n < 0)
{
std::cout<<"write error"<<std::endl;
return;
}
std::cout<<"SaveFile "<<_path + _filename<<":"<<str<<std::endl;
}
if(_next)
{
_next->Execute(str);
}
else
{
std::cout<<"end chain"<<std::endl;
}
}
private:
std::string _path;
std::string _filename;
int _fd;
};
HandlerTextBackUp:文件过大时,进行备份
cpp
#define MAX_LINE 5
class HandlerTextBackUp : public HandlerText
{
public:
HandlerTextBackUp(std::string path = DEFAULT_PATH,std::string filename = DEFAULT_FILENAME,int max_line = MAX_LINE)
:_ifs(path + filename),_path(path),_filename(filename),_max_line(max_line)
{
if(!_ifs.is_open())
{
std::cout<<"open file error"<<std::endl;
Unable();
return;
}
}
void Execute(std::string& str)override
{
if(_is_enable)
{
int line = 0;
std::string buffer;
if (!_ifs.is_open())
return;
while(std::getline(_ifs,buffer))
{
std::cout<<buffer<<std::endl;
line++;
}
if(line > MAX_LINE)
BackUp();
}
if(_next)
{
_next->Execute(str);
}
else
{
std::cout<<"end chain"<<std::endl;
}
}
void BackUp()
{
std::string newfile = _filename + "-" + std::to_string(time(nullptr));
if(fork() == 0)
{
chdir(_path.c_str());
rename(_filename.c_str(),newfile.c_str());
newfile += ".zip";
std::cout<<"remove "<<newfile<<" and zip it";
execlp("zip","zip","-r",newfile.c_str(),"./",NULL);
std::cout<<"execute error!"<<std::endl;
exit(1);
}
waitpid(-1,nullptr,0);
std::string tmp = _path + newfile;
remove(tmp.c_str());
}
private:
std::string _path;
std::string _filename;
std::ifstream _ifs;
int _max_line;
};
HandlerTextEntry:入口类
cpp
class HandlerTextEntry
{
public:
HandlerTextEntry()
{
_format = std::make_shared<HandlerTextFormat>();
_savefile = std::make_shared<HandlerTextSaveFile>();
_backup = std::make_shared<HandlerTextBackUp>();
_format->Set_next(_savefile);
_savefile->Set_next(_backup);
}
void Run(std::string& str)
{
if(_format)
_format->Execute(str);
}
private:
std::shared_ptr<HandlerText> _format;
std::shared_ptr<HandlerText> _savefile;
std::shared_ptr<HandlerText> _backup;
};
2.3.3 客户端和服务端设计
client.cpp:
cpp
#include "MsgQueue.hpp"
int main()
{
Client client;
while(1)
{
std::cout<<"input->";
std::string buffer;std::cin>>buffer;
client.Send(CLIENT_TYPE,buffer);
}
return 0;
}
server.cpp:
cpp
#include "MsgQueue.hpp"
#include "ChainOfResponsibility.hpp"
int main()
{
Server server;
HandlerTextEntry handler;
while(1)
{
std::string buffer;
buffer = server.Recv(CLIENT_TYPE);
if(buffer == "exit")
break;
std::cout<<"client say:"<<buffer<<std::endl;
handler.Run(buffer);
}
return 0;
}
3. 建造者模式和System V 信号量
3.1 什么是建造者模式
建造者模式(Builder Pattern)是一种创建型设计模式,它将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
模式结构
-
Product:要构建的复杂对象。
-
Builder:抽象接口,定义构建产品各部件的方法。
-
ConcreteBuilder:具体建造者,实现 Builder 接口,完成具体部件的构建。
-
Director:可选,负责按特定顺序调用 Builder 的方法来构建产品。
优点
-
将对象的构建过程与表示分离,易于扩展。
-
可以精细控制构建过程,逐步构造复杂对象。
-
适合参数较多、构建步骤复杂的对象。

3.2 System V 信号量
System V 信号量是 Unix/Linux 系统中经典的进程间同步机制,它允许进程对一组信号量(数组)进行操作,实现同步或互斥。与 POSIX 信号量不同,System V 信号量通常操作的是一个集合(一个信号量 ID 可以包含多个信号量单元)。
3.2.1 创建/获取信号量集
cpp
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
-
key:IPC 键值,通常由ftok()生成。 -
nsems:信号量集中信号量的个数(创建时必须指定,获取时可设为 0)。 -
semflg:权限标志,如IPC_CREAT、IPC_EXCL和权限位(如0666)。 -
返回值:成功返回信号量集标识符
semid,失败返回-1。
3.2.2 控制信号量
cpp
int semctl(int semid, int semnum, int cmd, ...);
-
semid:信号量集标识符。 -
semnum:信号量在集合中的索引(0 开始)。 -
cmd:控制命令,如:-
SETVAL:设置单个信号量的值。 -
GETVAL:获取单个信号量的值。 -
SETALL:设置所有信号量的值(需要第 4 个参数为unsigned short *数组)。 -
IPC_RMID:删除信号量集。 -
等等。
-
-
第 4 个参数是
union semun,需要用户自己定义:cppunion semun { int val; /* 用于 SETVAL */ struct semid_ds *buf; /* 用于 IPC_STAT、IPC_SET */ unsigned short *array; /* 用于 GETALL、SETALL */ struct seminfo *__buf; /* 用于 IPC_INFO */ };
3.2.3 操作信号量(P/V 操作)
cpp
int semop(int semid, struct sembuf *sops, size_t nsops);
-
sops:指向struct sembuf数组的指针。 -
nsops:操作的数量。 -
struct sembuf定义:cppstruct sembuf { unsigned short sem_num; /* 信号量索引 */ short sem_op; /* 操作数:>0 释放(V),<0 申请(P) */ short sem_flg; /* 标志,如 IPC_NOWAIT、SEM_UNDO */ };
sem_op 为正时,信号量值增加;为负时,信号量值减少(若绝对值大于当前值且未设置 IPC_NOWAIT,则进程阻塞);为 0 时,等待信号量值变为 0。
3.2.4 示例
cpp
#include <sys/sem.h>
#include <stdio.h>
union semun {
int val;
};
int main() {
key_t key = ftok("/tmp", 'A');
int semid = semget(key, 1, IPC_CREAT | 0666);
union semun arg;
arg.val = 1; // 初始化为 1(互斥)
semctl(semid, 0, SETVAL, arg);
struct sembuf p = {0, -1, 0}; // P 操作
struct sembuf v = {0, 1, 0}; // V 操作
semop(semid, &p, 1); // 进入临界区
// ... 临界区代码
semop(semid, &v, 1); // 离开临界区
semctl(semid, 0, IPC_RMID); // 删除
return 0;
}
3.2.5 C++封装接口
cpp
// 这里的Semaphore不是一个信号量!!而是一个信号量集合!!,要指明你要PV操作哪一个信号量!!
// 只考虑使用信号量的接口
class Semaphore
{
private:
void PV(int who, int data)
{
struct sembuf sem_buf;
sem_buf.sem_num = who; // 信号量编号,从0开始
sem_buf.sem_op = data; // S + sem_buf.sem_op
sem_buf.sem_flg = SEM_UNDO; // 不关心
int n = semop(_semid, &sem_buf, 1);
if (n < 0)
{
std::cerr << "semop PV failed" << std::endl;
}
}
public:
Semaphore(int semid) : _semid(semid)
{
}
int Id() const
{
return _semid;
}
void P(int who)
{
PV(who, -1);
}
void V(int who)
{
PV(who, 1);
}
~Semaphore()
{
if (_semid >= 0)
{
int n = semctl(_semid, 0, IPC_RMID);
if (n < 0)
{
std::cerr << "semctl IPC_RMID failed" << std::endl;
}
std::cout << "Semaphore " << _semid << " removed" << std::endl;
}
}
private:
int _semid;
// key_t _key; // 信号量集合的键值
// int _perm; // 权限
// int _num; // 信号量集合的个数
};
3.3 基于建造者模式的信号量
3.3.1 设计思路
需求:
- 父进程和子进程在信号量的约束下,有规律地打印数据
我们封装了Semaphore类,这个类建立在信号量已经建立好后,直接使用
那么信号量如何构建?
- SemaphoreBuilder:建造者模板类,提供虚函数接口:创建Key值,设计信号量个数等
- ConcreteSemaphoreBuilder:具体建造者类,继承SemaphoreBuilder,实现具体的方法,因此可以自定义
- Director:指挥类,ConcreteSemaphoreBuilder建造哪些部分,取决于Director。
3.3.2 建造者设计
SemaphoreBuilder:建造者模板类
cpp
#ifndef SEM_HPP
#define SEM_HPP
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
const std::string SEM_PATH = "/tmp";
const int SEM_PROJ_ID = 0x77;
const int defaultnum = 1;
#define GET_SEM (IPC_CREAT)
#define BUILD_SEM (IPC_CREAT | IPC_EXCL)
// 建造者接口
class SemaphoreBuilder
{
public:
virtual ~SemaphoreBuilder() = default;
virtual void BuildKey() = 0;
virtual void SetPermission(int perm) = 0;
virtual void SetSemNum(int num) = 0;
virtual void SetInitVal(std::vector<int> initVal) = 0;
virtual void Build(int flag) = 0;
virtual void InitSem() = 0;
virtual std::shared_ptr<Semaphore> GetSem() = 0;
};
ConcreteSemaphoreBuilder:具体实现的建造者类
cpp
// 具体建造者类
class ConcreteSemaphoreBuilder : public SemaphoreBuilder
{
public:
ConcreteSemaphoreBuilder() {}
virtual void BuildKey() override
{
// 1. 构建键值
std::cout << "Building a semaphore" << std::endl;
_key = ftok(SEM_PATH.c_str(), SEM_PROJ_ID);
if (_key < 0)
{
std::cerr << "ftok failed" << std::endl;
exit(1);
}
std::cout << "Got key: " << intToHex(_key) << std::endl;
}
virtual void SetPermission(int perm) override
{
_perm = perm;
}
virtual void SetSemNum(int num) override
{
_num = num;
}
virtual void SetInitVal(std::vector<int> initVal) override
{
_initVal = initVal;
}
virtual void Build(int flag) override
{
// 2. 创建信号量集合
int semid = semget(_key, _num, flag | _perm);
if (semid < 0)
{
std::cerr << "semget failed" << std::endl;
exit(2);
}
std::cout << "Got semaphore id: " << semid << std::endl;
_sem = std::make_shared<Semaphore>(semid);
}
virtual void InitSem() override
{
if (_num > 0 && _initVal.size() == _num)
{
// 3. 初始化信号量集合
for (int i = 0; i < _num; i++)
{
if (!Init(_sem->Id(), i, _initVal[i]))
{
std::cerr << "Init failed" << std::endl;
exit(3);
}
}
}
}
virtual std::shared_ptr<Semaphore> GetSem() override
{ return _sem; }
private:
bool Init(int semid, int num, int val)
{
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
} un;
un.val = val;
int n = semctl(semid, num, SETVAL, un);
if (n < 0)
{
std::cerr << "semctl SETVAL failed" << std::endl;
return false;
}
return true;
}
private:
key_t _key; // 信号量集合的键值
int _perm; // 权限
int _num; // 信号量集合的个数
std::vector<int> _initVal; // 初始值
std::shared_ptr<Semaphore> _sem; // 我们要创建的具体产品
};
Director:指挥者类
cpp
// 指挥者类
class Director
{
public:
void Construct(std::shared_ptr<SemaphoreBuilder> builder, int flag, int perm = 0666, int num = defaultnum, std::vector<int> initVal = {1})
{
builder->BuildKey();
builder->SetPermission(perm);
builder->SetSemNum(num);
builder->SetInitVal(initVal);
builder->Build(flag);
if (flag == BUILD_SEM)
{
builder->InitSem();
}
}
};
#endif // SEM_HPP
3.3.3 main函数设计
cpp
#include "Sem.hpp"
#include <unistd.h>
#include <ctime>
#include <cstdio>
int main()
{
// 基于抽象接口类的具体建造者
std::shared_ptr<SemaphoreBuilder> builder = std::make_shared<ConcreteSemaphoreBuilder>();
// 指挥者对象
std::shared_ptr<Director> director = std::make_shared<Director>();
// 在指挥者的指导下,完成建造过程
director->Construct(builder, BUILD_SEM, 0600, 3, {1, 2, 3});
// 完成了对象的创建的过程,获取对象
auto fsem = builder->GetSem();
// sleep(10);
// SemaphoreBuilder sb;
// auto fsem = sb.SetVar(1).build(BUILD_SEM, 1);
srand(time(0) ^ getpid());
pid_t pid = fork();
// 我们期望的是,父子进行打印的时候,C或者F必须成对出现!保证打印是原子的.
if (pid == 0)
{
director->Construct(builder, GET_SEM);
auto csem = builder->GetSem();
while (true)
{
// csem->P(0);
printf("C");
usleep(rand() % 95270);
fflush(stdout);
printf("C");
usleep(rand() % 43990);
fflush(stdout);
// csem->V(0);
}
}
while (true)
{
// fsem->P(0);
printf("F");
usleep(rand() % 95270);
fflush(stdout);
printf("F");
usleep(rand() % 43990);
fflush(stdout);
// fsem->V(0);
}
return 0;
}
4. 生产者消费者模型
4.1 什么是生产者消费者模型
生产者消费者模型是操作系统和多线程编程中的一个经典问题,在Linux环境下尤为常见。它描述了两类进程/线程(生产者与消费者)如何共享一个固定大小的缓冲区(通常称为"仓库"),并协调工作,避免数据竞争、死锁或资源浪费。
简单来说,这是一个同步与互斥的模型,用于解决"速度不匹配"或"解耦"的问题。
模型中包含三个核心要素:
-
生产者:负责生成数据。如果缓冲区满了,生产者必须停止生产(阻塞),直到消费者取走数据。
-
消费者:负责处理数据。如果缓冲区空了,消费者必须停止消费(阻塞),直到生产者放入新数据。
-
缓冲区:一段有限的共享内存(如数组、链表)。它起到解耦和缓冲的作用,允许生产者和消费者以不同的速度运行。

4.2 基于BlockingQueue的生产者消费者模型
4.2.1 设计思路
在多线程编程中阻塞队列(BlockingQueue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空 时,从队列获取元素的操作 将会被阻塞,直到队列中被放入了元素;当队列满 时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
4.2.1 模型设计
cpp
#pragma once
#include "../pthread/pthread.hpp"
#include "../cond/cond.hpp"
#include <queue>
template<class T>
class blockqueue{
public:
blockqueue(size_t capacity)
:_capacity(capacity)
{}
void push(T data)
{
_lock.lock();
while(_q.size() >= _capacity)
{
_psize++;
_producer_queue.wait(_lock.get_lock());
_psize--;
}
_q.push(data);
if(_csize > 0)
_consumer_queue.signal();
_lock.unlock();
}
T pop()
{
_lock.lock();
while(_q.size() == 0)
{
_csize++;
_consumer_queue.wait(_lock.get_lock());
_csize--;
}
T data = _q.front();
_q.pop();
if(_psize > 0)
_producer_queue.signal();
_lock.unlock();
return data;
}
~blockqueue()
{}
private:
std::queue<T> _q;
size_t _capacity;
mylock _lock;
mycond _producer_queue;
mycond _consumer_queue;
size_t _psize = 0;
size_t _csize = 0;
};