目录
信号量
信号量相关接口
创建信号量
初始化信号量
等待信号量,P操作
发布信号量,V操作
销毁信号量
基于信号量的环形队列下的生产者和消费者模型
环形队列
代码实现
上期我们学习了线程同步的概念,掌握了基于阻塞队列的生产者和消费者模型。基于阻塞队列的生产者和消费者模型,三种关系,两个角色,一个场所,现在我们要关注的就是这一个场所,在此模型中,我们把阻塞队列称为临界资源,但是基于此模型,有且仅有这一个临界资源,也就是说临界资源的数目只有这一个。但是很多场景下临界资源的数目并不只有一个,本期我们主要研究的就是临界资源不只有一个的场景下的生产者和消费者模型。
信号量
我们之前学习了临界资源的概念,临界资源其实就是可以被多个执行流访问的资源。上节课的生产者和消费者模型,只有超市这一个临界资源。还有一个场景,就是电影院这个场景,在电影中,电影院的座位可以被多个顾客使用,所以电影院的座位就是临界资源,且电影院的座位不单单只有一个,所以临界资源是有多个的。那么信号量是什么呢?
信号量用于描述临界资源的数目的大小,比如在电影院的模型下,信号量就可以表示座位的多少。 任何线程要访问临界资源,必须先申请信号量,访问完临界资源,必须释放信号量。申请信号量我们称为P操作,释放信号量我们称为V操作。
通过伪代码为大家讲述p操作和v操作。
当然并不是所有临界资源都可以用信号量来表示其数目的多少。当一个临界资源可以被细分时,我们才可以用临界资源来表示临界资源的数目的多少,当一个临界资源被分成了多个小的临界资源时,此时就可以实现多个执行流共同访问临界资源,从而实现,多个执行流的并发,从而提高代码的执行效率。
信号量相关接口
信号量的接口与互斥锁接口和条件变量接口类似。
创建信号量
sem_t sem; //创建信号量
初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化信号量
sem为创建的信号量的地址;pshared,0表示线程间共享,非0表示进程间共享;value表示信号量的初始值。
等待信号量,P操作
int sem_wait(sem_t *sem); //P()
等待信号量,表示申请信号量,要使用资源。将信号量减1。
发布信号量,V操作
sem_post(sem_t *sem); //V()
发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
销毁信号量
int sem_destroy(sem_t *sem); //销毁信号量
基于信号量的环形队列下的生产者和消费者模型
环形队列
上图为一个环形队列,有生产者和消费者两个角色,环形队列为一个大的临界资源,这个大的临界资源有被分成了多个小部分,为多份小的临界资源。
代码实现
生产者往环形队列中生产数据,消费者从环形队列中消费数据。生产者每生产一个数据前进一格,当生产者和消费者相遇时,要么是环形队列为空,要么是环形队列中的数据已满。基于此,我们对基于环形队列的生产者和消费者模型设立两个规则。
规则一:生产者不能把消费者逃一圈,因为一旦套了一圈,势必会造成相遇时,生产者和消费者所处的环形队列的那一块空间中数据的覆盖,造成数据丢失。
规则二:因为刚开始环形队列是为空的,所以刚开始一定是生产者先生产,然后再是消费者再进行消费,正是基于此,往后消费者的位置一定是不能超过生产者的。
生产者看重的是环形队列中的空白位置的数目,而消费者看重的是环形队列中数据的数目。所以创建两个信号量,一个信号量描述环形队列中空位置的数目,一个信号量描述环形队列中数据的数目。
代码实现如下。
RingQueue.hpp
#include <iostream>
#include <vector>
#include <semaphore.h>namespace yjd
{const int DefaultCapacity = 10;template <class T>class RingQueue{public:RingQueue(const int &capacity = DefaultCapacity): _v(capacity){_capacity = capacity;sem_init(&_blank_sem, 0, 10);sem_init(&_data_sem, 0, 0);_c_step = _p_step = 0;}~RingQueue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);}void push(T &data){sem_wait(&_blank_sem);_v[_p_step] = data;sem_post(&_data_sem);_p_step++;_p_step % _capacity;}void pop(T *data){sem_wait(&_data_sem);*data = _v[_c_step];sem_post(&_blank_sem);_c_step++;_c_step%_capacity;}private:std::vector<T> _v;int _capacity;// 描述空位置的数目sem_t _blank_sem;// 描述数据的数目sem_t _data_sem;int _p_step;int _c_step;};
}
整个代码的逻辑为,生产者先生产数据,然后消费者消费数据。生产者要生产数据,先申请一个空位置信号量,空位置信号量减1,然后生产数据,生产完数据之后,数据信号量加1;消费者消费数据时,先申请数据信号量,数据信号量减1,消费完数据之后,多了一个空位置,空位置信号量加1。
RingCP.cc
#include "RingQueue.hpp"
#include <pthread.h>
#include <unistd.h>
#include <time.h>using namespace yjd;void *producter(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (true){int data = rand() % 20 + 1;rq->push(data);std::cout << "生产者生产数据 " << data << std::endl;}
}void *consumer(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (true){int data = 0;rq->pop(&data);std::cout << "消费者消费数据 " << data << std::endl;sleep(1);}
}int main()
{pthread_t c;pthread_t p;RingQueue<int> *rq = new RingQueue<int>();srand((long long)time(nullptr));pthread_create(&c, nullptr, consumer, (void *)rq);pthread_create(&p, nullptr, producter, (void *)rq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
运行结果如下。
通过运行结果可知,我们通过使用信号量和唤醒队列,实现了生产和消费者的同步。
以上便是本期信号量的相关知识点。
本期内容到此结束 ^_^