Linux多线程(信号量与环形队列)

举报
卖寂寞的小男孩 发表于 2022/10/23 09:31:23 2022/10/23
【摘要】 本文主要介绍Linux多线程中信号量与环形队列

@[toc]

一、信号量

1.信号量概念

在学习SystemV的时候,就提到过信号量这一概念。
信号量的本质就是一个计数器,用来描述临界资源数目的大小(即有多少资源可以分配给线程)。使用信号量的本质其实是预订资源(预订后才能使用)。可以通过信号量让不同的线程访问临界资源的不同区域,从而实现并发。

2.信号量系统接口

如果我们使用普通的计数器count来记录临界资源,显然count也是临界资源,因此是不安全的。所以OS提供了一系列与信号量有关的接口:

(1)sem_init

在这里插入图片描述
用于对信号量进行初始化,第一个参数为指向一个信号量的指针,第二个函数代表是否进程之间共享,这里设为0即可。第三个参数表示信号量的数量,需要我们自己设置。

(2)sem_destroy

在这里插入图片描述
即销毁信号量,参数为要销毁的信号量。

(3)sem_wait

在这里插入图片描述
sem_wait操作执行的是信号量–的操作:
用伪代码可以表示为:

start:
lock();
if(count<=0)
{
wait;//挂起
}
else
{
count--;
}

(4)sem_post

在这里插入图片描述
sem_post执行的是信号量++的操作。
它的伪代码如下:

lock();
count++;
unlock();

二、环形队列

环形队列最早在数据结构那里学习过,我们使用数组来模拟环形队列,当遍历到数组结尾的时候,回到数组首元素位置。
在这里插入图片描述
当拿和放指向同一个位置的时候,环形队列既可能是空,也可能是满。因此我们需要作一个判断。
1.可以定义一个镂空的位置,当当前位置+1!=拿的时候可以放,否则队列已满,不再放。
2.可以定义一个计数器,每放一次就++,每拿一次就–,根据计数器的值来判断队列是否已满。

三、利用信号量和环形队列来实现生产者消费者模型

使用信号量和环形队列的好处在于,可以让消费者和生产者可以不同时访问同一块临界资源,因此不需要加锁。

1.实现思路

(1)同步与互斥

当生产者和消费者指向同一个位置的时候,队列可能为空也可能为满,因此当队列不为空,不为满的时候生产者和消费者一定指向的不是同一个位置。因此生产和消费可以并发执行,不用加锁,符合互斥特性。
当队列为空时,生产者执行,队列为满时,消费者执行,这也体现了局部上的同步特性。

(2)规则

生产者关心的是环形队列中空的位置,消费者关心的是环形队列中的数据。

1.生产者不能对消费者扣圈。
2.消费者不能超过生产者。
3.当指向同一个位置的时候,要根据空或者满的状态来判断让谁先执行。

(3)信号量思路

在这里插入图片描述
当生产者生产数据后,blank信号量–,data信号量++。当有data后,消费者开始消耗数据,即data信号量–,blank信号量++。从而实现环状结构。

2.具体实现

(1)ring_queue.hpp

#include<iostream>
#include<vector>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>
#include<stdlib.h>
using namespace std;
namespace ns_ring_queue
{
    template<class T>
    class RingQueue
    {
    private:
        vector<T> ring_queue_;
        int cap_;
        sem_t blank_sem_;//空格资源信号量
        sem_t data_sem_;//数据资源信号量
        int c_step_;//消费者位置
        int p_step_;//生产者位置
        pthread_mutex_t con;
        pthread_mutex_t pro;
    public:
        RingQueue(int cap=10):cap_(cap),ring_queue_(cap)
        {
            sem_init(&blank_sem_,0,cap);
            sem_init(&data_sem_,0,0);
            c_step_=p_step_=0;
        }
        void Push(const T& in)
        {
            sem_wait(&blank_sem_);
            pthread_mutex_lock(&pro);
            ring_queue_[p_step_]=in;
             p_step_++;
            p_step_%=cap_;
            pthread_mutex_unlock(&pro);
            sem_post(&data_sem_);
        }
        void Pop(T* out)
        {
            sem_wait(&data_sem_);
            pthread_mutex_lock(&con);
            *out=ring_queue_[c_step_];
            c_step_++;
            c_step_%=cap_;
            pthread_mutex_unlock(&con);
            sem_post(&blank_sem_);
        }
        ~RingQueue()
        {
            sem_destroy(&blank_sem_);
            sem_destroy(&data_sem_);
        }    
    };
}

(2)ring_cp.cc

#include"ring_queue.hpp"
using namespace std;
using namespace ns_ring_queue;
void* consumer(void* args)
{
    RingQueue<int>* rq=(RingQueue<int>*)args;
    while(true)
    {
        int data=0;
        rq->Pop(&data);
        cout<<"正在被线程"<<pthread_self()<<"消费的数据是:"<<data<<endl;
        sleep(1);
    }
}
void* productor(void* args)
{
    RingQueue<int>* rq=(RingQueue<int>*)args;
    while(true)
    {
        int data=rand()%20+1;
        cout<<"正在被线程"<<pthread_self()<<"生产"<<"生产的数据是:"<<data<<endl;
        rq->Push(data);
    }
}
int main()
{
    RingQueue<int>* rq=new RingQueue<int>();
    pthread_t c1,c2,c3,c4,p1,p2,p3;
    pthread_create(&c1,nullptr,consumer,(void*)rq);
    pthread_create(&c2,nullptr,consumer,(void*)rq);
    pthread_create(&c3,nullptr,consumer,(void*)rq);
    pthread_create(&c4,nullptr,consumer,(void*)rq);
    pthread_create(&p1,nullptr,productor,(void*)rq);
    pthread_create(&p2,nullptr,productor,(void*)rq);
    pthread_create(&p3,nullptr,productor,(void*)rq);
    pthread_join(c1,nullptr);
    pthread_join(c2,nullptr);
    pthread_join(c3,nullptr);
    pthread_join(c4,nullptr);
    pthread_join(p1,nullptr);
    pthread_join(p2,nullptr);
    pthread_join(p3,nullptr);
}

注意,由于生产者消费者不会访问同一块资源,因此消费者和生产者之间不用加锁。而生产者和生产者之间,消费者和消费者之间要访问同一块资源,因此需要进行加锁。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。