MSIPO技术圈 首页 IT技术 查看内容

Linux——信号量

2024-03-21

目录

POSIX信号量

信号量原理

信号量概念

信号量函数

基于环形队列的生产者消费者模型

生产者和消费者申请和释放资源

单生产者单消费者

多生产者多消费者

多生产者多消费者的意义

信号量的意义


POSIX信号量

信号量原理

  1. 如果仅用一个互斥锁对临界资源进行保护,相当于把这块临界资源看作一个整体,同一时间只允许一个执行流对这块临界资源进行访问
  2. 如果将这块临界资源再分成多个区域,当多个执行流需要访问临界资源时,如果这些执行流访问的是临界资源的不同区域,那就可以继续并发访问,访问同一个资源的时候再进行同步和互斥

信号量概念

        信号量本质上就是一个计数器,记录的是临界资源中资源数目的计数器,它可以更细粒度的对临界资源进行管理。

        访问临界资源的时候,先申请信号量信号量的值--,这叫做预定资源,我们叫做P操作;使用完后,信号量的值++,这叫做释放资源,我们叫做V操作

        当我们申请了一个信号量,可以保证当前执行流一定具有一个资源,要说是哪一个那就需要我们自己编写代码决定了。

信号量函数

作用:初始化信号量

参数:

  • sem:需要初始化的信号量
  • pshared:传入0表示线程间共享,传入非0表示进程间共享
  • value:信号量的初始值(类似一个计数器)

返回值:成功返回0,失败返回-1,错误码被设置。

 

作用:销毁信号量

参数:要销毁的信号量

返回值:成功返回0,失败返回-1,错误码被设置。

 

作用:等待信号量

参数:要等待的信号量

返回值:

  • 等待信号量成功返回0,信号量的值减一。
  • 等待信号量失败返回-1,信号量的值保持不变。 

 

作用:释放信号量

参数:需要释放的信号量

返回值:

  • 发布信号量成功返回0,信号量的值加一。
  • 发布信号量失败返回-1,信号量的值保持不变。

基于环形队列的生产者消费者模型

        生产者最看重的就是队列中的空间,而消费者最看重的就是队列中的数据。只要有空间就可以生产数据,只要有数据就可以消费。

        用信号量来表示上述的数据:

  • spaceSem:表示有多少空间,开始设为N(队列长度)。
  • dataSem:表示有多少数据,开始设为0。

生产者和消费者申请和释放资源

        当生产数据的时候:

P(spaceSem) 
    spaceSem--;
// 生产数据
V(dataSem)
    dataSem++;

        当消费数据的时候

P(dataSem)
    dataSem--;
// 消费数据
V(spaceSem)
    spaceSem++;

        当两个执行流同时访问的时候:

  • 如果消费者先执行,要P(申请)dataSem(数据),但是一开始的dataSem的值是0,所以就被阻塞了。
  • 如果生产者先运行,要P(申请)spaceSem(空间),一开始的spaceSem的值是N,就可以申请成功,生产数据,之后V(释放)dataSem(数据)。
  • 如果生产者把数据放满了,要P(申请)spaceSem(空间)就会失败,生产者就被阻塞。
  • 如果两个执行流都能获取想要的资源,那就可以实现并发访问。

单生产者单消费者

// Sem.hpp
#include <iostream>
#include <pthread.h>
#include <semaphore.h>

// 封装一下信号量
class Sem
{
public: 
    Sem(int value)
    {
        sem_init(&_sem, 0, value);
    }
    void P()
    {
        sem_wait(&_sem);
    }
    void V()
    {
        sem_post(&_sem);
    }
    ~Sem()
    {
        sem_destroy(&_sem);
    }
private:
    sem_t _sem; 
};
// ringQueue.hpp
#include <iostream>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include "Sem.hpp"

using namespace std;

const int g_default_num = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int default_num = g_default_num)
        :_ring_queue(default_num)
        ,_num(default_num)
        ,_c_step(0)
        ,_p_step(0)
        ,_space_sem(default_num)
        ,_data_sem(0)
    {}
    ~RingQueue()
    {}
    // 生产者
    void push(const T& in)
    {
        _space_sem.P();
        _ring_queue[_p_step++] = in;
        _p_step %= _num;
        _data_sem.V();
    }
    // 消费者
    void pop(T* out)
    {
        _data_sem.P();
        *out = _ring_queue[_c_step++];
        _c_step %= _num;
        _space_sem.V();
    }
    void debug()
    {
        cout << "size: " << _num << "queue: " << _ring_queue.size() << endl;
    }

private:
    vector<T> _ring_queue;
    size_t _num;
    int _c_step; // 消费者下标
    int _p_step; // 生产者下标
    Sem _space_sem; // 空间信号量
    Sem _data_sem; // 数据信号量
};
// testMain.cc

#include "ringQueue.hpp"
#include <ctime>

void* consumer(void* args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while (true)
    {
        int x;
        // 1.从环形队列中获取任务
        rq->pop(&x);
        // 2.进行处理
        cout << "消费:" << x << endl;
        // sleep(1);
    }
}

void* productor(void* args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while (true)
    {
        // 1.构建数据或任务对象,数据可能从任何地方来,那一定会有时间消耗
        int x = rand() % 10 + 1;
        // 2.推送到环形队列
        rq->push(x);
        cout << "生产:" << x << endl;
        // sleep(1);
    }
}

int main()
{
    srand((unsigned)time(nullptr) ^ getpid());

    RingQueue<int>* rq = new RingQueue<int>();

    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void*)rq);
    pthread_create(&p, nullptr, productor, (void*)rq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    return 0;
}

        与基于阻塞队列的生产者消费者模型不同的是,阻塞队列是一块临界资源,就会有互斥和同步的问题,生产者和消费者访问临界资源的时候就要加互斥锁来保护临界资源,用信号量实现没有使用互斥锁,我们把资源的数目规定好,通过管理这些资源的数量,就可以对每一块资源更细粒度的管理。
        关于环形队列的实现就不过多赘述了,就是控制下标加上模运算。生产者消费者只要访问的不是环形队列中的相同区域,他们两个基本就没有关系,所以可以实现并发访问。我们维护的只有生产者和消费者之间的互斥和同步关系

多生产者多消费者

        如果是多生产和多消费该怎么做呢?我们要知道的是相比于单生产单消费要多维护什么关系,其实就是生产和生产间、消费和消费间的这两种互斥关系

        如果只加一把锁,本来生产和消费可以有很大概率并发执行,现在又多了锁的竞争,就可能变成串行执行,一把不行,那就加两把。

        生产者之间的临界资源就是空间,消费者之间的临界资源就是数据。

        既然有了锁就可以保护临界资源了,那么我是先申请信号量还是先申请锁呢?假如先申请锁,锁申请成功了,再申请信号量,此时就可能有很多信号量还没有分配出去,前面我们也说过,这个信号量是一种预定机制,即便申请了信号量也没有使用资源,那为何不先申请信号量呢,所以一般都是先申请信号量再加锁。

 

const int g_default_num = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int default_num = g_default_num)
        :_ring_queue(default_num)
        ,_num(default_num)
        ,_c_step(0)
        ,_p_step(0)
        ,_space_sem(default_num)
        ,_data_sem(0)
    {
        pthread_mutex_init(&_clock, nullptr);
        pthread_mutex_init(&_plock, nullptr);
    }
    ~RingQueue()
    {
        pthread_mutex_destroy(&_clock);
        pthread_mutex_destroy(&_plock);
    }
    // 生产者
    void push(const T& in)
    {
        _space_sem.P();
        pthread_mutex_lock(&_plock);
        _ring_queue[_p_step++] = in;
        _p_step %= _num;
        pthread_mutex_unlock(&_plock);
        _data_sem.V();
    }
    // 消费者
    void pop(T* out)
    {
        _data_sem.P();
        pthread_mutex_lock(&_clock);
        *out = _ring_queue[_c_step++];
        _c_step %= _num;
        pthread_mutex_unlock(&_clock);
        _space_sem.V();
    }
    void debug()
    {
        cout << "size: " << _num << "queue: " << _ring_queue.size() << endl;
    }

private:
    vector<T> _ring_queue;
    size_t _num;
    int _c_step;            // 消费者下标
    int _p_step;            // 生产者下标
    Sem _space_sem;         // 空间信号量
    Sem _data_sem;          // 数据信号量
    pthread_mutex_t _clock; // 消费者之间的锁
    pthread_mutex_t _plock; // 生产者之间的锁
};

多生产者多消费者的意义

        其实生产者往容器缓冲区中放数据和消费者从容器缓冲区中拿数据,就是一个生产者在放,一个消费者在拿,那它的意义在哪呢?

        我们要思考的是,我们从哪里拿到的任务也就是生产任务前,我们拿到任务后该怎么做,如果只有一个执行流,它既要做这个也要做那个,中间还得加锁,那任务就是一个一个做的,如果使用多线程,那么多个线程就可以并发的处理这些动作。

信号量的意义

        信号量的意义是什么呢?

        看到这里是一定会带有问题的,阻塞队列时,我们要先申请锁,再检测,不成功就阻塞,唤醒后在检测,成功后再执行,但是使用信号量都没有检测,甚至可能都没有加锁。

        其实阻塞队列中我们并不清楚临界资源的情况,但信号量是一个计数器,它可以预定某种资源,在PV操作中我们也可以知道临界资源的情况。

相关阅读

热门文章

    手机版|MSIPO技术圈 皖ICP备19022944号-2

    Copyright © 2024, msipo.com

    返回顶部