• Linux线程信号量


    信号量基本性质

    信号量的本质实际上就是一把计数器,可以用来描述临界资源中资源数目的大小,即最多能有多少资源可以分配给线程。
    临界资源可以被划分为一个一个的小资源,如果处理得当,有可能让多个线程同时访问临界资源的不同区域,从而实现并发。每个线程想访问临界资源,都需要先申请信号量资源,使用这个计数器可以实现对临界资源的预定机制。

    信号量的基本操作函数

    信号量初始化

    在这里插入图片描述
    信号量也是一种特殊的数据类型—sem_t,信号量需要对其进行初始化操作,上图是对信号量进行初始化的函数,使用该参数需要控制的只有第一个和第三个参数
    即第一个参数传入需要进行初始化的信号量
    第二个参数pshared中0表示线程间共享,非零表示进程间共享,这里设置为0即可
    第三个参数表示信号量初始值,上面说过信号量实际上是一把计数器,这里是设置计数器的大小。

    信号量的销毁

    在这里插入图片描述
    信号量不仅需要手动进行初始化申请,而且在使用完成后还需要对其进行手动销毁释放。上图是信号量的销毁函数,这里只需要传入所需要销毁的信号量即可。

    信号量相关操作

    信号量对应的相关操作函数一般只有两种:即P操作和V操作
    这里P操作是进行申请信号量,对应计数器的–操作
    V操作是进行释放信号量操作,对应计数器的++操作
    下面分别来介绍信号的P操作和V操作相对应的操作函数。

    sem_wait

    在这里插入图片描述
    上图中sem_wait是等待信号量,会将信号量的值减1,相当于是P操作,该函数也比较简单,直接传入需要操作的信号量即可

    sem_post

    在这里插入图片描述
    上图所示的操作是释放信号量的函数接口,即V操作,表示资源使用完毕,可以归还资源,即会将信号量的值加1,这里也只需要传入需要进行操作的信号量即可
    下面来使用信号量操作一个环形队列来实现一个生产者消费者模型,来实际使用一下信号量。

    使用信号量实现生产者消费者模型

    环形队列基本原理

    环形队列有多种实现方式,他既可以使用数组来实现环形队列,也可以使用链表来实现环形队列。这里主要介绍使用数组来实现环形队列的方式。
    用数组来实现环形队列,需要先设置数组的大小。其次需要镂空一个位置,从而能够方便判空判满。
    环形队列数组需要使用两个数组下标来进行控制,一个是代表拿的位置即队列的头,另一个代表放数据的位置即队列的尾。如果这两个下标指向同一个位置,则此时代表队列为空,如果放的位置下标+1刚好等于拿的位置下标,则此时队列为满,不能再进行放数据操作了。

    生产者消费者模型基本思想

    生产者和消费者在开始的时候指向的是同一个位置,在队列为满的时候生产者和消费者也是指向同一个位置。
    队列为空或者为满的时候不能让生产者和消费者同时进行,即此时两者为互斥和同步关系,因为队列为空的时候消费者就不能再进行消费了,而队列为满的时候生产者也不能再进行生产了,所以为空的时候要让生产者执行,为满的时候要让消费者执行。
    当队列不为空也不为满的情况下,生产者和消费者指向的一定不是同一个位置,这种情况下生产和消费可以同时并发执行。
    生产者需要关心的是环形队列中空的位置
    消费者需要关系的是环形队列中的数据
    生产者和消费者需要满足以下规则:
    规则1:生产者不能把消费者套一个圈(领先一圈)
    规则2:消费者不能超过生产者
    规则3:当指向同一个位置的时候,要根据空、满状态,来判断让谁先执行
    其他:除此以外,生产者和消费者可以并发执行

    基本实现代码

    下面是环形队列的基本实现代码,这里是使用一个模板类进行实现,队列中可以存放各种类型的数据,基本数据类型这里使用的是stl中的vector容器来实现数组结构。需要设置相关的数组大小以及信号量大小。信号量需要设置两个,一个是代表数组中空位置资源,另一个代表数组中的数据个数。另外为了针对多生产者和多消费者的情况,这里还需要申请两把锁分别给生产者和消费者。最后设置两个变量即c_step_和p_step_作为下标来判断此时环形队列中的情况。
    信号量和锁的申请释放在环形队列中的构造和析构函数中完成即可
    环形队列中最关键的就是下面的Push和Pop接口函数,这两个函数一个是出数据一个是放数据,分别对应的是生产者和消费者。
    其中Push的实现主要需要注意的是每放进去一个数据,需要对空位置进行P操作,并对数据信号量进行V操作,然后将数据放入数组中,再对p_step_进行++操作,为了防止下标越界,可以对下标进行取模操作
    Pop操作的实现主要需要注意的是每拿出一个数据,需要对空位置进行V操作,并对数据位置进行P操作,并用输出型参数接收取出的值。最后对c_step_进行++操作,为了防止下标越界,需要对下标进行取模操作。

    #include<iostream>
    #include<vector>
    #include<semaphore.h>
    #include<pthread.h>
    namespace ns_ring_queue
    {
        const int g_cap_default = 10;
        template<class T>
        class RingQueue
        {
            private:
            std::vector<T> ring_queue_;
            int cap_;
            //生产者关心空位置资源
            sem_t blank_sem_;
            //消费者关心数据资源
            sem_t data_sem_;
    
            int c_step_;
            int p_step_;
    
            pthread_mutex_t c_mtx_;
            pthread_mutex_t p_mtx_;
    
    
            public:
            RingQueue(int cap = g_cap_default)
                :cap_(cap)
                ,ring_queue_(cap)
            {
                sem_init(&blank_sem_,0,cap);
                sem_init(&data_sem_,0, 0);
                c_step_=p_step_ = 0;
                pthread_mutex_init(&c_mtx_,nullptr);
                pthread_mutex_init(&p_mtx_,nullptr);
    
            }
    
    
            ~RingQueue()
            {
                sem_destroy(&blank_sem_);
                sem_destroy(&data_sem_);
                pthread_mutex_destroy(&c_mtx_);
                pthread_mutex_destroy(&p_mtx_);
    
            }
    
            public:
            //多生产和多消费的优势在于并发的获取和处理任务
            void Push(const T& in)
            {
                //生产接口
                sem_wait(&blank_sem_);//P(空位置)
                pthread_mutex_lock(&p_mtx_);
                //可以生产,但是需要判断往哪里生产
                ring_queue_[p_step_] = in;
                //p_step_也变成了临界资源
                p_step_++;
                p_step_ %= cap_;
                pthread_mutex_unlock(&p_mtx_);
                sem_post(&data_sem_);//V(数据)
                
    
                
            }
    
            void Pop(T* out)
            {
                //消费接口
                sem_wait(&data_sem_);
                pthread_mutex_lock(&c_mtx_);
                *out = ring_queue_[c_step_];
                c_step_++;
                c_step_ %= cap_;
                pthread_mutex_unlock(&c_mtx_);
                sem_post(&blank_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
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    基本测试代码

    下面是生产者和消费者模型使用环形队列实现的基本测试代码,这里使用了多个生产者和消费者对象进行放数据和拿数据的操作,下面来看一下运行结果。

    #include"ring_queue.hpp"
    #include<pthread.h>
    #include<time.h>
    #include<unistd.h>
    using namespace ns_ring_queue;
    
    void* consumer(void* args)
    {
        RingQueue<int>* rq = (RingQueue<int>*)args;
        while(true)
        {
            //rq->pop
            int data = 0;
            rq->Pop(&data);
            std::cout << "消费数据是:"<< data <<"我是:"<< pthread_self()<< std::endl;
            sleep(1);
        }
    }
    
    
    void* producter(void* args)
    {
        RingQueue<int>* rq = (RingQueue<int>*)args;
        while(true)
        {
            //rq->push
            int data = rand()% 20 + 1;
            std::cout<<"生产数据是:"<<data <<"我是:"<< pthread_self()<< std::endl;
            rq->Push(data);
            sleep(1);
        }
    }
    
    int main()
    {
        srand((long long)time(nullptr));
        RingQueue<int>* rq = new RingQueue<int>();
        pthread_t c0,c1,c2,c3,p0,p1,p2;
        pthread_create(&c0,nullptr,consumer,(void*)rq);
        pthread_create(&c1,nullptr,producter,(void*)rq);
        pthread_create(&c2,nullptr,producter,(void*)rq);
        pthread_create(&c3,nullptr,producter,(void*)rq);
        pthread_create(&p0,nullptr,producter,(void*)rq);
        pthread_create(&p1,nullptr,producter,(void*)rq);
        pthread_create(&p2,nullptr,producter,(void*)rq);
    
    
        pthread_join(c0,nullptr);
        pthread_join(c1,nullptr);
        pthread_join(c2,nullptr);
        pthread_join(c3,nullptr);
    
        pthread_join(p0,nullptr);
        pthread_join(p1,nullptr);
        pthread_join(p2,nullptr);
    
    
    
        return 0;
    }
    
    • 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
    • 56
    • 57
    • 58
    • 59
    • 60

    在这里插入图片描述

  • 相关阅读:
    Java开发面试--MongoDB专区
    node应用故障定位顶级技巧—动态追踪技术[Dynamic Trace]
    解决el-checkbox点击文字也会选中
    博客园商业化之路-开发任务众包平台:召集早期合作开发者
    Android Studio里的C/C++返回: ld: error: undefined symbol
    【JVM】垃圾回收机制中,对象进入老年代的触发条件
    libusb 源码移植到工程项目中,使用CMake编译
    【41. 最短编辑距离(线性DP)】
    Docker本地部署开源浏览器Firefox并远程访问进行测试
    SQL Server重置自增序列初始值
  • 原文地址:https://blog.csdn.net/h1091068389/article/details/125491739