【Linux】信号量与生产消费模型

我们已经实现过锁+条件变量的PC模型
但是BlockingQueue并不能进行生产与消费的并发,原因在于我们使用的是STL提供的队列,进行了一个适配,底层的实现可能会修改到成员变量造成未知的错误。

而这次我们选择使用环形队列(底层vector),可以实现生产与消费的并发。

POSIX信号量:

POSIX比System V版本的信号量更加简洁明了。
首先他们都是在在这里插入图片描述头文件下的

我们来简单的看一下接口:
关于信号量的一些详细概念可以看一次进程间通信

信号量初始化:
在这里插入图片描述
第一个参数是你创建的sem对象地址,第二个参数由于我们是进程间的线程通信,设置为0即可,第三个是你信号量“计数器”个数。

关于第二个参数更详细的可以直接看参考文档。在这里插入图片描述
信号量销毁:
在这里插入图片描述
传入你的信号量对象地址即可。

P操作:
在这里插入图片描述
V操作:
在这里插入图片描述

生产消费模型:

数据结构中的环形队列:

环形队列在数据结构中有一个很讲究的点,那就是如何判断空与满。
在这里插入图片描述
当添加完一圈元素后,头与尾又指向了同一个位置
在这里插入图片描述
因此我们的解决方案通常是增加一个空节点进行判断或者增加一个计数。

但是利用信号量 + 环形队列实现PC即可解决这个判断问题,因为信号量本身就是一个计数器。

理论:

那我们该如何利用环形队列实现PC?

我们采用先单消费单生产进行,这样只需考虑生产者与消费者的关系。

在这里插入图片描述

代码(单生产单消费):

由于我们底层是使用vector模拟,因此在进行放数据或者拿数据时需要每次都进行%=一个N,防止超出vector范围。
RingQueue.hpp

#include <iostream>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <vector>

const int defaultnum = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
public:
    RingQueue(int cap = defaultnum) : _cap(cap), _p_index(0), _c_index(0), _ringqueue(cap)
    {
        sem_init(&_sem_data, 0, _cap);
        sem_init(&_sem_space, 0, _cap);
    }
    ~RingQueue()
    {
        sem_destroy(&_sem_data);
        sem_destroy(&_sem_space);
    }
    void Push(const T &in)
    {
        P(_sem_space);
        _ringqueue[_p_index++] = in;
        _p_index %= _cap;
        V(_sem_data);
    }
    void Pop(T* out)
    {
        P(_sem_data);
        *out = _ringqueue[_c_index++];;
        _p_index %= _cap;
        V(_sem_space);
    }

private:
    std::vector<T> _ringqueue;
    int _cap;

    int _p_index;
    int _c_index;

    sem_t _sem_data;
    sem_t _sem_space;
};

Main.cc

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

void *Consumer(void *args)
{
    while (true)
    {
        sleep(1);
        RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
        // 接收数据
        int data = 0;
        rq->Pop(&data);
        // 处理数据
        std::cout << "Consumer->" << data << std::endl;
    }
}
void *Producer(void *args)
{
    while (true)
    {
        RingQueue<int> *rq = static_cast<RingQueue<int> *>(args);
        // 生产数据
        int data = rand() % 10 + 1;
        rq->Push(data);
        std::cout << "Producer->" << data << std::endl;
    }
}
int main()
{
    srand(time(nullptr));
    RingQueue<int> rq;
    pthread_t p, c;
    pthread_create(&c, nullptr, Consumer, &rq);
    pthread_create(&p, nullptr, Producer, &rq);

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

    return 0;
}

注意,这里我们的数据依然可以传内置类型,自定义类型,或者可调用对象。

代码(多生产多消费):

多生产多消费势必带来生产与生产的关系,消费与消费的联系。

也就意味着我们要进行加锁保护,因为多个生产者线程同时访问vector与_c_index这些共享资源,因此需要加锁。

我们此时有两个加锁方案(在P之前,V之后加锁解锁或者P之后V之前)

    void Push(const T &in)
    {
        Lock(_p_mutex);
        P(_sem_space);
        _ringqueue[_p_index++] = in;
        _p_index %= _cap;
        V(_sem_data);
        Unlock(_p_mutex);
    }
    void Pop(T *out)
    {
        Lock(_c_mutex);
        P(_sem_data);
        *out = _ringqueue[_c_index++];
        _p_index %= _cap;
        V(_sem_space);
        Unlock(_c_mutex);
    }

我们怎么选择?
我们的PV操作本质是一种预定机制,放在加锁之前可以让所有的线程共同申请信号量,达到申请并发,所以我们选择P之后V之前加锁方案。

#include <iostream>
#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>
#include <vector>
#include <mutex>

const int defaultnum = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
    void Lock(pthread_mutex_t &mtx)
    {
        pthread_mutex_lock(&mtx);
    }
    void Unlock(pthread_mutex_t &mtx)
    {
        pthread_mutex_unlock(&mtx);
    }

public:
    RingQueue(int cap = defaultnum) : _cap(cap), _p_index(0), _c_index(0), _ringqueue(cap)
    {
        sem_init(&_sem_data, 0, _cap);
        sem_init(&_sem_space, 0, _cap);

        pthread_mutex_init(&_p_mutex, nullptr);
        pthread_mutex_init(&_c_mutex, nullptr);
    }
    ~RingQueue()
    {
        sem_destroy(&_sem_data);
        sem_destroy(&_sem_space);

        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }
    void Push(const T &in)
    {
        P(_sem_space);
        Lock(_p_mutex);
        _ringqueue[_p_index++] = in;
        _p_index %= _cap;
        Unlock(_p_mutex);
        V(_sem_data);
    }
    void Pop(T *out)
    {
        P(_sem_data);
        Lock(_c_mutex);
        *out = _ringqueue[_c_index++];
        _p_index %= _cap;
        Unlock(_c_mutex);
        V(_sem_space);
    }
private:
    std::vector<T> _ringqueue;
    int _cap;

    int _p_index;
    int _c_index;

    sem_t _sem_data;
    sem_t _sem_space;
    pthread_mutex_t _p_mutex;
    pthread_mutex_t _c_mutex;
};

完~

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux信号可以用来实现生产者-消费模型,保证生产者和消费者之间的同步和互斥。下面是一个基于信号生产者-消费模型的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 10 int buffer[BUFFER_SIZE]; sem_t empty, full; pthread_mutex_t mutex; void *producer(void *arg) { int item = 0; while (1) { // 等待空闲缓冲区 sem_wait(&empty); // 获取互斥锁 pthread_mutex_lock(&mutex); // 生产数据项 item++; // 将数据项放入缓冲区 buffer[(item - 1) % BUFFER_SIZE] = item; printf("Produced item %d\n", item); // 释放互斥锁 pthread_mutex_unlock(&mutex); // 发送信号通知缓冲区中已有数据 sem_post(&full); } } void *consumer(void *arg) { int item; while (1) { // 等待缓冲区中有数据 sem_wait(&full); // 获取互斥锁 pthread_mutex_lock(&mutex); // 从缓冲区取出数据项 item = buffer[item % BUFFER_SIZE]; printf("Consumed item %d\n", item); // 释放互斥锁 pthread_mutex_unlock(&mutex); // 发送信号通知缓冲区有空闲 sem_post(&empty); } } int main(int argc, char *argv[]) { pthread_t ptid, ctid; // 初始化信号和互斥锁 sem_init(&empty, 0, BUFFER_SIZE); sem_init(&full, 0, 0); pthread_mutex_init(&mutex, NULL); // 创建生产者和消费者线程 pthread_create(&ptid, NULL, producer, NULL); pthread_create(&ctid, NULL, consumer, NULL); // 等待线程结束 pthread_join(ptid, NULL); pthread_join(ctid, NULL); return 0; } ``` 在这个示例中,用两个信号`empty`和`full`分别表示缓冲区的空闲和有数据状态。生产者线程在生产数据项之前,会等待`empty`信号,表示缓冲区中有空闲的位置。生产完成后,会释放一个`full`信号,表示缓冲区中有数据了。消费者线程在消费数据项之前,会等待`full`信号,表示缓冲区中有数据。消费完成后,会释放一个`empty`信号,表示缓冲区中有空闲位置了。在生产者和消费者之间,用一个互斥锁`mutex`来保护共享缓冲区的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值