Linux-----信号量

网友投稿 676 2022-05-30

信号量原理

之前我们知道被多个执行流同时访问的公共资源叫做临界资源,而临界资源不保护的话会造成数据不一性的问题。

之前我们用互斥锁保护临界资源是把这个临界资源当做一个整体,只能让1个执行流访问临界资源。现在我们把临界资源分割成多个区域,当多个执行流访问不同的区域,此时不会出现数据不一性的问题了。

信号量概念

信号量本质就是一个计数器,描述临界资源有效个数的计数器。

每个执行流先申请信号量,申请到信号量后同时访问临界资源,访问完后释放信号量。

信号量的PV操作:

P操作:我们将申请信号量的操作叫做P操作,申请信号量的本质就是有权限访问临界资源,申请成功后,P操作的本质就是让计数器–即可

V操作:将释放信号量叫做V操作,归还临界资源,V操作的本质是让计数器++

PV操作必须是原子的

执行流要申请信号量要先看到信号量,所以信号量本身就是临界资源。信号量是保护临界资源的,我们不能再用信号量去保护信号量,所以信号量的操作必须是原子的

申请信号量失败挂起等待

当执行流申请信号量时,可能此时信号量为0,说明信号量描述的临界资源被申请完了,那么这个执行流就要挂起等待,在信号量等待队列中等待,直到有信号量释放被唤醒

为什么使用信号量?

这样可以把临界资源分成多分,多执行流并行执行,提高了效率。

如何使用信号量呢?下面来看看信号量的一些函数

信号量函数

初始化信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

pshared:0表示线程间共享,非零表示进程间共享

value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

int sem_wait(sem_t *sem); //P()

等待信号量,会将信号量的值-1

释放信号量

int sem_post(sem_t *sem);//V()

释放信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。

基于环形队列的生产消费模型

空间和数据资源

生产者关注的是空间资源,消费者关心的是数据资源

只要环形队列中有空间,生产者就可以进行生产

而环形队列中有数据,消费者就可以消费数据

我们不防设空间资源为block_sem,数据资源为data_sem,那么它们的初始值怎么设置呢?

现在是用信号量来描述队列中的空间和数据资源,刚开始队列中是没有数据的,所以给block_sem的初始值设为队列的空间,data_sem的初始值是0,因为刚开始队列为空没有数据的。

生产者和消费者申请、释放信号量

生产者申请空间资源,释放数据资源

生产者的操作步骤:

1.如果block_sem不为0,说明队列中有空间资源,生产者申请信号量成功,那么对应的操作就是P(block_sem),V(data_sem)。此时队列中多了1块空间,那么data_sem就要–,也就是V(data_sem)

2.如果block_sem为0,那么生产者申请信号量失败,此时生产者就要挂起等待,等待有新的空间资源

消费者申请数据资源,释放空间资源

消费者的操作步骤和生产者基本一样

1.消费者申请data_sem,若data_sem不为0,消费者申请信号量成功,对应的操作时P(data_sem),那么V(block_sem),释放的就是空间资源,因为数据占了相应的空间

Linux-----信号量

2.若data_sem,消费者申请信号量失败,消费者挂起等待,等待新的数据资源。

生产者和消费者要遵守的规则

1.快的不能把慢的套1个圈

2.慢的不能超过快的

若生产者的速度比消费者的快,当生产者把队列生产满了,并再次遇到消费者。此时生产者再继续往前生产,那么再生产的数据就会覆盖掉,此时生产者就要挂起等待

同样的道理,消费者的速度快,当消费者把数据都消费完了再进行消费就会消费到垃圾数据,此时应该挂起等待生产者继续生产数据

模拟实现基于环形队列的生产者消费者模型

我们用STL中的vector来模拟环形队列,分为RunQueue.hpp和mian.cc

相关说明:

RunQueue.hpp

我们需要2个下标来标识生产者和消费者的位置,需要生产者消费者申请的资源,block_sem和data_sem

我们要提供2个接口,分别是入队列和出队列,生产者的P操作,P(block_sem),V(data_sem),消费者的P(data_sem),V(data_sem)

每当生产者生产1个空间,对应的生产者下标++,还要模上队列的空间,以防越界,消费者也是相同的操作

当2个下标指向相同的位置时,要么是刚开始为空,要么是其中1个速度快。当为空时,一定有1个申请信号量失败,同样当1个要超过令个是再申请信号量也会失败。因为信号量的本身就是1个计数器。

1 #pragma once 2 3 #include 4 #include 5 #include 6 #include 7 #include 8 class Task 9 { 10 public: 11 int x; 12 int y; 13 public: 14 Task(int _x = 1,int _y = 10) 15 16 :x(_x) 17 ,y(_y) 18 {} 19 int run() 20 { 21 return x+y; 22 } 23 ~Task(){} 24 }; 25 template 26 class RunQueue 27 { 28 private: 29 std::vector v; 30 int c_index; 31 int p_index; 32 int cap; 33 sem_t block_sem; 34 sem_t data_sem; 35 public: 36 RunQueue(int _cap = 6) 37 :cap(_cap) 38 ,c_index(0) 39 ,p_index(0) 40 { 41 sem_init(&block_sem,0,cap); 42 sem_init(&data_sem,0,0); 43 v.resize(10); 44 } 45 void Push( T& data) 46 { 47 sem_wait(&block_sem); 48 v[p_index] = data; 49 p_index++; 50 p_index %= cap; 51 sem_post(&data_sem); 52 } 53 void Pop(T& data) 54 { 55 sem_wait(&data_sem); 56 data = v[c_index]; 57 c_index++; 58 c_index %= cap; 59 sem_post(&block_sem); 60 } 61 ~RunQueue() 62 { 63 sem_destroy(&block_sem); 64 sem_destroy(&data_sem); 65 } 66 67 68 };

main.cc

主函数的话,创建2个线程,生产者生产数据,消费者消费数据即可

1 #include"RunQueue.hpp" 2 void* Consumer(void* arg) 3 { 4 //RunQueue* rq = (RunQueue*)arg; 5 RunQueue* rq = (RunQueue*)arg; 6 while(true) 7 { 8 int x,y; 9 Task t; 10 rq->Pop(t); 11 std::cout<<"Consumer done:"<* rq = (RunQueue*)arg; 18 RunQueue* rq = (RunQueue*)arg; 19 while(true) 20 { 21 int x = rand() % 10 + 1; 22 int y = rand() % 100 + 1; 23 Task t(x,y); 24 rq->Push(t); 25 std::cout<<"Product done:"< *rq = new RunQueue; 31 32 pthread_t c,p; 33 pthread_create(&c,nullptr,Consumer,rq); 34 pthread_create(&c,nullptr,Product,rq); 35 36 pthread_join(c,nullptr); 37 pthread_join(p,nullptr); 38 delete rq; 39 return 0; 40 }

先让消费者慢,生产者一下就把任务生产完,消费者开始做任务

这次让消费者快,生产者慢

刚开始没有数据,消费者刚进入申请数据资源失败挂起等待,生产者申请空间成功,生产者生产一个任务,消费者消费一个任务。

如果想要是多生产者和多消费者参考我上一篇生产者和消费者即可,这里博主就不加了。

linux

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:一些常用的知识点
下一篇:mac安装的vagrant访问laraval欢迎页面,执行时间15秒,安装nfs挂载点(亲测可行)
相关文章