Linux线程(五)

信号量

上一期讲了关于生产者消费者模型,但是在代码中仍有不足之处

pthread_mutex_lock(&_mutex);
while(is_full())
{
    pthread_cond_wait(&_pcond,&_mutex);//如果生产条件不满足,无法生产,需要等待
}
_q.push(in)
pthread_cond_signal(&_ccond);
pthread_mutex_unlock(&_mutex);

对于上诉代码,一个线程在操作临界资源的时候,必须临界资源是需要满足条件的,但是对于公共资源是否满足生产或者消费条件,无法直接得知(在没有访问之前,是无法得知的),所以只能先加锁,在检测,在操作,在解锁,也就是说在操作临界资源的时候,有可能临界资源没有就绪,但是我们无法提前知道,所以只能对资源整体加锁,就默认了对资源整体使用。实际的情况可能存在:一份公共资源允许你访问不用的区域。因此引入了信号量。

什么是信号量?信号量的本质是一个计数器,只要拥有信号量,就在未来一定能够拥有资源的一部分,申请信号量的本质:对临界资源中某一块资源的预定机制。

为什么要有信号量:需要通过信号量来评估资源的使用率,在访问临界资源的之前,需要通过信号量去申请访问临界资源的资格。通过信号量的方式,我们在访问临界资源的时候,就可以提前知道临界资源的使用情况。

对于信号量而言,线程要访问临界资源中的某一区域,就需要申请信号量,而对于信号量,所有人都需要看到看到这一部分信号量,那么信号量也是公共资源。对于信号量,只要申请成功,就一定有你的资源,只要申请失败,就说明条件不就绪,只能等待。

关于信号量的一些函数:

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value); //信号量的初始化
// pshared=0 表示线程间共享,非0表示线程间不共享
// value 信号量的初始量


int sem_destroy(sem_t *sem);

//功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()

//对于信号量而言,本质是一个计数器-递增or递减 

sem--;    //申请 资源  --必须保证操作的原子性   --P操作
sem++;    //归还资源   --必须保证操作的原子性   --V操作

为了代码编写信号量的问题,采用了环形队列

为了完成环形队列的生产消费模型,需要做的核心工作:1、消费者的下标不能超过生产者的下标;2、生产者不能把消费者套圈生产;3、最开始的时候一定是先生产,在消费。

信号量在环形队列中充当的角色就是:衡量临界资源中资源的数量。对于生产者而言,看重的是队列中剩余的空间,空间资源定义为一个信号量,对于消费者而言,看中的是放入队列中的数据,数据资源定义为一个信号量。

在环形队列中,大部分情况下,单生产和单消费是并行执行的,只有在满或者空的时候,才有互斥和同步的情况。

代码如下:

//RingQueue.h
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#include <cassert>

const int getcap = 5;
template <class T>
class ring_queue
{
private:
    // P操作主要是对数据量进行预定
    void P(sem_t &sem)
    {
        int n = sem_wait(&sem);
        assert(n == 0);
        (void)n;
    }
    //主要是对信号量进行还原
    void V(sem_t &sem)
    {
        int n = sem_post(&sem);
        assert(n == 0);
        (void)n;
    }
public:
    ring_queue(const int cap = getcap) : _queue(cap), _cap(cap)
    {
        // 初始化信号量
        int n = sem_init(&_productor_sem, 0, _cap); // 初始化信号量,注意这里对生产者数量的初始化时剩余的可以放置任务线程的个数
        assert(n == 0);
        (void)n;
        n = sem_init(&_comsumer_sem, 0, 0);       //初始化消费者生产量,注意这里是对消费者可以消费的数量进行初始化,初始应该为0
        assert(n == 0);
        (void)n;
        //对消费者和生产者的位置进行初始化
        _spacePostion = _dataPostion = 0;

        //对锁进行初始化,这里需要两把锁,对于 生产者和消费者而言,他们是互斥的
        pthread_mutex_init(&_productor_mutex,nullptr);
        pthread_mutex_init(&_comsumer_mutex,nullptr);

    }
    void Push(const T &in)
    {
        // 主要是进行PV操作,首先需要明确的是PV操作是原子性的
        //首先对资源进行预定
        P(_productor_sem);
        
        //对这里进行加锁的时候需要注意,在P操作之后加锁,在V操作之前解锁
        //因为PV操作是原子性的,所以可以不用加锁,而且加锁的话会影响其余线程提前对资源的预定,浪费时间
        //如果在P操作之前加锁,那么必须等这个线程在释放锁之后,其余线程才能申请资源在进行后续操作 ,比较浪费时间
        //生产者消费者模型节约时间主要是在 在该生产者线程进行操作的时候,其余线程可以先去预定资源,申请任务,以及后续的在任务执行完成之后,并行完成后续的一系列操作
        pthread_mutex_lock(&_productor_mutex);
        _queue[_spacePostion++]=in;
        _spacePostion%=_cap;
        pthread_mutex_unlock(&_productor_mutex);

        V(_comsumer_sem); //消费者可以消费的资源+1

    }
    void Pop(T *out)
    {
        //主要是对消费者进行PV操作,首先需要注意的是这里需要对消费者P操作,生产者V操作
        //首先消费者对资源进行消费预定
        P(_comsumer_sem);
        pthread_mutex_lock(&_comsumer_mutex);
        *out = _queue[_dataPostion++];
        _dataPostion%=_cap;
        pthread_mutex_unlock(&_comsumer_mutex);
        V(_productor_sem); //生产者可以生产的资源+1
    }
    ~ring_queue()
    {
        // 销毁信号量
        sem_destroy(&_productor_sem);
        sem_destroy(&_comsumer_sem);
        //对锁进行销毁
        pthread_mutex_destroy(&_productor_mutex);
        pthread_mutex_destroy(&_comsumer_mutex);

    }

private:
    std::vector<T> _queue;
    int _cap;
    sem_t _productor_sem;
    sem_t _comsumer_sem;
    int _spacePostion;
    int _dataPostion;
    pthread_mutex_t _productor_mutex;
    pthread_mutex_t _comsumer_mutex;

};
//Task.hpp
#include <iostream>
#include <functional>
#include <string>
class Task
{
public:
    using func_t = std::function<int(int, int,char)>;

public:
    Task() {} // 无参构造函数
    Task(int x, int y, char op, func_t func) : _x(x), _y(y), _op(op), _func(func)
    {
    }
    std::string operator()()
    {
        int result = _func(_x,_y,_op);
        char buffer[128];
        snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
        return buffer;
    }

    std::string Tostring()
    {
        char namebuffer[1024];
        snprintf(namebuffer, sizeof namebuffer, "%d %c %d = ?", _x, _op, _y);
        return namebuffer;
    }

private:
    int _x;
    int _y;
    char _op;
    func_t _func;
};

const std::string opera = "+-*/%";
int myMath(int x, int y, char op)
{
    int result;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
        if (y == 0)
        {
            std::cout << "DIV error" << std::endl;
            result = -1;
        }
        result = x / y;
        break;
    case '%':
        if (y == 0)
        {
            std::cout << "Mod error" << std::endl;
            result = -1;
        }
        result = x % y;
        break;
    default:
        break;
    }
    return result;
}
//main.cc
#include "RingQueue.hpp"
#include <ctime>
#include <unistd.h>
#include <sys/types.h>
#include "Task.hpp"

void *productor_routinue(void *args)
{
    // //version1  单生产 单消费
    // ring_queue<int> *rq = static_cast<ring_queue<int> *>(args);
    // while(true)
    // {
    //     //产生数据,放入到环形队列中
    //     sleep(3);
    //     int data = rand()%10+1;
    //     rq->Push(data);
    //     std::cout<<"生产者生产数据: "<<data<<std::endl;

    // }
    // return nullptr;

    //version 2 多生产 多消费 并且任务改为具体的函数
    ring_queue<Task> *rq = static_cast<ring_queue<Task> *>(args);
    while(true)
    {
        int x = rand()%10+1;
        int y = rand()%5+1;
        char op=opera[rand()%5];
        Task t(x, y, op, myMath);
        rq->Push(t);
        std::cout<<"生产者生产数据: "<<t.Tostring()<<std::endl;
        
    }
    return nullptr;

}
void *comsumer_routinue(void *args)
{
    // ///version 1 单生产 单消费
    // ring_queue<int> *rq = static_cast<ring_queue<int> *>(args);
    // //开始push和pop
    // while(true)
    // {
    //     int data;
    //     rq->Pop(&data);
    //     std::cout<<"消费者消费数据 :"<<data<<std::endl;
    //     // sleep(1);
    // }
    // return nullptr;
    //version 2 多生产 多消费 并且任务改为具体的函数
    ring_queue<Task> *rq = static_cast<ring_queue<Task> *>(args);
    while(true)
    {   
        Task t;
        rq->Pop(&t);
        std::cout<<"消费者消费数据: "<<t()<<std::endl;
    }
    return nullptr;
}

int main()
{
    //对于单生产,单消费而言,只有生产和消费之间的竞争关系
    //pthread_t c, p;   
    //如果对于多生产和多消费而言,满足321原则,3种竞争关系:生产与生产之间,消费与消费之间,生产和消费之间的竞争和  2种角色:生产者和消费者 1种公共资源:环形队列
    //此时 对于这个环形队列而言,就是一个公共资源,需要加锁,防止多个生产线程同时进去,改变n
    pthread_t c[4],p[5];

    srand((unsigned int)time(nullptr)^getpid()^pthread_self());  //抛下随机数种子

    //ring_queue<int> *rq = new ring_queue<int>();
    ring_queue<Task> *rq = new ring_queue<Task>();
    // 创建消费者和生产者线程
    for(int i=0;i<5;i++)  pthread_create(p+i, nullptr, productor_routinue, rq);
    for(int i=0;i<4;i++)  pthread_create(c+i, nullptr, comsumer_routinue, rq);

    for(int i=0;i<5;i++)  pthread_join(p[i], nullptr);
    for(int i=0;i<4;i++)  pthread_join(c[i], nullptr);

    delete rq;

    return 0;
}

设计模式

什么是设计模式:针对一些常见的场景,给定一些对应的解决方案,这个就是设计模式

单例模式

某一个类,只有一个对象(实例),就称之为单例模式,例如在服务器开发场景中,经常需要让服务器加载很多的数据到内存,此时就需要一个单例来管理这些数据。对于单例模式需要把对应的赋值重载函数和拷贝构造函数给禁止掉。并且构造函数放入到private里面,析构函数依然是public的,只留一个构造实例的结构用于外界访问

    void operator=(const ThreadPool&) = delete;
    ThreadPool(const ThreadPool &)    = delete;

有两种实现方式:饿汉实现方式和懒汉实现方式:

其中比如洗碗的例子:吃完饭,立刻洗碗的方式,下一次就能直接拿碗吃饭的这就是饿汉方式

吃完饭,立刻把碗洗了,下一顿吃饭用到了这个碗在洗碗,就是懒汉方式。懒汉最核心的思想就是“延时加载”,从而优化服务器的启动速度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值