Yupureki:个人主页✨个人专栏:《C》 《算法》《Linux系统编程》《高并发内存池》《MySQL数据库》《个人在线OJ平台》Yupureki的简介:目录1. 单例模式1.1 什么是单例模式1.2 饿汉模式1.3 懒汉模式2. 责任链模式与消息队列2.1 什么是责任链模式2.2 消息队列2.2.1 创建/获取队列2.2.2 发送消息2.2.3 接收消息2.2.4 控制操作2.2.5 示例2.2.6 C封装接口2.3 基于责任链模式的消息队列2.3.1 设计思路2.3.2 责任链设计2.3.3 客户端和服务端设计3. 建造者模式和System V 信号量3.1 什么是建造者模式3.2 System V 信号量3.2.1 创建/获取信号量集3.2.2 控制信号量3.2.3 操作信号量P/V 操作3.2.4 示例3.2.5 C封装接口3.3 基于建造者模式的信号量3.3.1 设计思路3.3.2 建造者设计3.3.3 main函数设计4. 生产者消费者模型4.1 什么是生产者消费者模型4.2 基于BlockingQueue的生产者消费者模型4.2.1 设计思路4.2.1 模型设计1. 单例模式1.1 什么是单例模式单例模式是一种创建型设计模式它保证一个类在程序生命周期内只有一个实例并提供一个全局访问点来获取该实例。在 C 中实现单例时通常需要将构造函数、拷贝构造函数、赋值运算符声明为private或delete防止外部创建多个对象。通过一个静态成员函数如getInstance()返回唯一实例的引用或指针。根据实例创建时机的不同单例模式分为饿汉模式和懒汉模式。1.2 饿汉模式在程序启动时或编译期就创建唯一实例通过静态成员变量保存。优点简单、线程安全C11 保证静态局部变量初始化是线程安全的但此处用的是静态成员变量其在程序启动时初始化同样线程安全。缺点无论是否使用都会创建实例可能造成资源浪费。实现方式:构造函数和拷贝构造函数必须私有化类内部成员必须包含静态实例化成员在类外初始化需要提供返回静态实例化成员的函数通过静态实例化成员进行访问类内的函数#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版本C11 前需要借助std::atomic和内存屏障实现较复杂。C11 及之后推荐方法利用静态局部变量的线程安全特性简洁可靠。实现方式:构造函数和拷贝构造函数必须私有化类内部成员必须包含静态实例化成员的指针变量在类外初始化第一次通过get_instance返回实例时判断指针为空加锁随后通过new开辟空间通过静态实例化成员进行访问类内的函数#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 创建/获取队列int msgget(key_t key, int msgflg);key通常使用ftok()生成。msgflg权限标志可组合IPC_CREAT等。返回值成功返回消息队列标识符失败返回-1。2.2.2 发送消息int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);msgp指向消息结构的指针消息结构必须包含一个long mtype成员后跟数据。struct msgbuf { long mtype; // 消息类型正整数 char mtext[1]; // 实际数据 };msgszmtext的大小不包含mtype。msgflgIPC_NOWAIT表示非阻塞。2.2.3 接收消息ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);msgtyp选择接收的消息类型。等于0则接收队列中第一条消息大于0接收类型等于该值的消息小于0接收类型小于等于其绝对值的消息。msgflgIPC_NOWAIT、MSG_NOERROR截断过长消息。2.2.4 控制操作int msgctl(int msqid, int cmd, struct msqid_ds *buf);cmdIPC_RMID删除队列IPC_STAT获取状态等。2.2.5 示例示例发送和接收 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封装接口#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::coutftok errorstd::endl; return; } std::coutkey:_keystd::endl; } void Create(int flag)//创建/获取消息队列 { _msqid msgget(_key,flag); if(_msqid 0) { std::coutmsgget errorstd::endl; return; } std::coutmsqid:_msqidstd::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::coutmsgsnd errorstd::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-HandlerTextBackUp2.3.2 责任链设计HandlerText:模板类Execute:责任链中处理者的自定义处理方式_is_enable:处理的权限如果没有直接传递给下一个_next:指向下一个处理者的指针class HandlerText { public: virtual void Execute(std::string str) 0; void Set_next(std::shared_ptrHandlerText next) { _next next; } void Enable() { _is_enable true; } void Unable() { _is_enable false; } protected: bool _is_enable true; std::shared_ptrHandlerText _next; };HandlerTextFormat:对内容拼接上时间进程pid信息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::coutFormat: strstd::endl; } if(_next) { _next-Execute(str); } else { std::coutend chainstd::endl; } } private: };HandlerTextSaveFile:将内容保存到文件中#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::coutopen file errorstd::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::coutwrite errorstd::endl; return; } std::coutSaveFile _path _filename:strstd::endl; } if(_next) { _next-Execute(str); } else { std::coutend chainstd::endl; } } private: std::string _path; std::string _filename; int _fd; };HandlerTextBackUp:文件过大时进行备份#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::coutopen file errorstd::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::coutbufferstd::endl; line; } if(line MAX_LINE) BackUp(); } if(_next) { _next-Execute(str); } else { std::coutend chainstd::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::coutremove newfile and zip it; execlp(zip,zip,-r,newfile.c_str(),./,NULL); std::coutexecute 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:入口类class HandlerTextEntry { public: HandlerTextEntry() { _format std::make_sharedHandlerTextFormat(); _savefile std::make_sharedHandlerTextSaveFile(); _backup std::make_sharedHandlerTextBackUp(); _format-Set_next(_savefile); _savefile-Set_next(_backup); } void Run(std::string str) { if(_format) _format-Execute(str); } private: std::shared_ptrHandlerText _format; std::shared_ptrHandlerText _savefile; std::shared_ptrHandlerText _backup; };2.3.3 客户端和服务端设计client.cpp:#include MsgQueue.hpp int main() { Client client; while(1) { std::coutinput-; std::string buffer;std::cinbuffer; client.Send(CLIENT_TYPE,buffer); } return 0; }server.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::coutclient say:bufferstd::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创建/获取信号量集#include sys/sem.h int semget(key_t key, int nsems, int semflg);keyIPC 键值通常由ftok()生成。nsems信号量集中信号量的个数创建时必须指定获取时可设为 0。semflg权限标志如IPC_CREAT、IPC_EXCL和权限位如0666。返回值成功返回信号量集标识符semid失败返回-1。3.2.2控制信号量int semctl(int semid, int semnum, int cmd, ...);semid信号量集标识符。semnum信号量在集合中的索引0 开始。cmd控制命令如SETVAL设置单个信号量的值。GETVAL获取单个信号量的值。SETALL设置所有信号量的值需要第 4 个参数为unsigned short *数组。IPC_RMID删除信号量集。等等。第 4 个参数是union semun需要用户自己定义union 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 操作int semop(int semid, struct sembuf *sops, size_t nsops);sops指向struct sembuf数组的指针。nsops操作的数量。struct sembuf定义struct sembuf { unsigned short sem_num; /* 信号量索引 */ short sem_op; /* 操作数0 释放V0 申请P */ short sem_flg; /* 标志如 IPC_NOWAIT、SEM_UNDO */ };sem_op为正时信号量值增加为负时信号量值减少若绝对值大于当前值且未设置IPC_NOWAIT则进程阻塞为 0 时等待信号量值变为 0。3.2.4 示例#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封装接口// 这里的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:建造者模板类#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::vectorint initVal) 0; virtual void Build(int flag) 0; virtual void InitSem() 0; virtual std::shared_ptrSemaphore GetSem() 0; };ConcreteSemaphoreBuilder:具体实现的建造者类// 具体建造者类 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::vectorint 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_sharedSemaphore(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_ptrSemaphore 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::vectorint _initVal; // 初始值 std::shared_ptrSemaphore _sem; // 我们要创建的具体产品 };Director:指挥者类// 指挥者类 class Director { public: void Construct(std::shared_ptrSemaphoreBuilder builder, int flag, int perm 0666, int num defaultnum, std::vectorint initVal {1}) { builder-BuildKey(); builder-SetPermission(perm); builder-SetSemNum(num); builder-SetInitVal(initVal); builder-Build(flag); if (flag BUILD_SEM) { builder-InitSem(); } } }; #endif // SEM_HPP3.3.3 main函数设计#include Sem.hpp #include unistd.h #include ctime #include cstdio int main() { // 基于抽象接口类的具体建造者 std::shared_ptrSemaphoreBuilder builder std::make_sharedConcreteSemaphoreBuilder(); // 指挥者对象 std::shared_ptrDirector director std::make_sharedDirector(); // 在指挥者的指导下完成建造过程 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 模型设计#pragma once #include ../pthread/pthread.hpp #include ../cond/cond.hpp #include queue templateclass 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::queueT _q; size_t _capacity; mylock _lock; mycond _producer_queue; mycond _consumer_queue; size_t _psize 0; size_t _csize 0; };