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


    一、信号量

    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--;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (4)sem_post

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

    lock();
    count++;
    unlock();
    
    • 1
    • 2
    • 3

    二、环形队列

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

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

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

    1.实现思路

    (1)同步与互斥

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

    (2)规则

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

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

    (3)信号量思路

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

    2.具体实现

    (1)ring_queue.hpp

    #include
    #include
    #include
    #include
    #include
    #include
    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_);
            }    
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    (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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

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

  • 相关阅读:
    教师工作量管理系统思路(链表应用)
    面试题 25. 合并两个排序的链表
    ubuntu20.04 编译内核源码5.15.58
    Sigmoid类神经网络的鲁棒性验证
    将折叠屏卖到5999元!但摩托罗拉razr 2022的看点不止性价比
    数据挖掘十大算法--Apriori算法
    Xilinx FPGA 7系列 GTX/GTH Transceivers (5)-- Aurora 8b10b 信号传输实战--小试牛刀
    Windows系统如何部署Wing FTP Server与公网远程访问【内网穿透】
    全志A40i开发板硬件说明书——100%国产+工业级方案(中)
    四书五经 中庸
  • 原文地址:https://blog.csdn.net/qq_51492202/article/details/126072775