《Linux系统编程》20.常见程序设计模式

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》《MySQL数据库》

《个人在线OJ平台》


🌸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++ 中实现单例时,通常需要:

  • 将构造函数、拷贝构造函数、赋值运算符声明为 privatedelete,防止外部创建多个对象。

  • 通过一个静态成员函数(如 getInstance())返回唯一实例的引用或指针。

根据实例创建时机的不同,单例模式分为饿汉模式懒汉模式

1.2 饿汉模式

在程序启动时(或编译期)就创建唯一实例 ,通过静态成员变量保存。

  • 优点:简单、线程安全(C++11 保证静态局部变量初始化是线程安全的,但此处用的是静态成员变量,其在程序启动时初始化,同样线程安全)。
  • 缺点:无论是否使用都会创建实例,可能造成资源浪费。

实现方式:

  1. 构造函数和拷贝构造函数必须私有化
  2. 类内部成员必须包含静态实例化成员,在类外初始化
  3. 需要提供返回静态实例化成员的函数
  4. 通过静态实例化成员进行访问类内的函数
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 及之后推荐方法:利用静态局部变量的线程安全特性,简洁可靠。

实现方式:

  1. 构造函数和拷贝构造函数必须私有化
  2. 类内部成员必须包含静态实例化 成员的指针变量,在类外初始化
  3. 第一次通过get_instance返回实例时判断指针为空,加锁,随后通过new开辟空间
  4. 通过静态实例化成员进行访问类内的函数
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 成员,后跟数据。

    cpp 复制代码
    struct msgbuf {
        long mtype;       // 消息类型(正整数)
        char mtext[1];    // 实际数据
    };
  • msgszmtext 的大小(不包含 mtype)。

  • msgflgIPC_NOWAIT 表示非阻塞。

2.2.3 接收消息

cpp 复制代码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • msgtyp:选择接收的消息类型。等于0则接收队列中第一条消息;大于0接收类型等于该值的消息;小于0接收类型小于等于其绝对值的消息。

  • msgflgIPC_NOWAITMSG_NOERROR(截断过长消息)。

2.2.4 控制操作

cpp 复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • cmdIPC_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端处理文件时,在责任链上传递

  1. HandlerTextFormat:对内容拼接上时间,进程pid信息
  2. HandlerTextSaveFile:将内容保存到文件中
  3. HandlerTextBackUp:文件过大时,进行备份
  4. 这些类具有相同点:处理和传递,因此我们采用继承加多态的方式,设计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_CREATIPC_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,需要用户自己定义:

    cpp 复制代码
    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 操作)

cpp 复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);
  • sops:指向 struct sembuf 数组的指针。

  • nsops:操作的数量。

  • struct sembuf 定义:

    cpp 复制代码
    struct 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类,这个类建立在信号量已经建立好后,直接使用

那么信号量如何构建?

  1. SemaphoreBuilder:建造者模板类,提供虚函数接口:创建Key值,设计信号量个数等
  2. ConcreteSemaphoreBuilder:具体建造者类,继承SemaphoreBuilder,实现具体的方法,因此可以自定义
  3. 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;
};
相关推荐
M1nat0_2 小时前
Linux基础 Ext 文件系统:从磁盘硬件到目录路径的全链路解析
linux·服务器·网络·数据库
AIminminHu2 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(一-2-(3)-当你的协同CAD服务器面临“千人同屏”时:从单机优化到分布式高并发)
运维·服务器·分布式
moical2 小时前
关于docker-compose启动elasticsearch:7.17.29报"permission denied": unknown错误解决
linux
s6516654962 小时前
编译linux内核
linux
誰能久伴不乏2 小时前
给开发板装上嘴巴与耳朵:i.MX6ULL 裸机串口 (UART) 驱动终极指南
arm开发·c++·单片机·嵌入式硬件·arm
biter down2 小时前
深入浅出 C++ string 类:从原理到实战
开发语言·c++
济6172 小时前
ARM Linux 驱动开发篇:阻塞与非阻塞IO详解(含等待队列+poll机制)--- Ubuntu20.04
linux·嵌入式·嵌入式linux驱动开发
上海云盾安全满满2 小时前
游戏被攻击了要如何选择防护,接高防服务器还是游戏盾
服务器·网络·游戏
舰长1152 小时前
Diffie-Hellman Key Agreement Protocol 资源管理错误漏洞(CVE-2022-40735)【原理扫描】openssl升级
运维·服务器