POSIX信号量

POSIX信号量

POSIX信号量用于同步操作,达到无冲突访问临界资源的目的,可以用于线程之间的通信。而信号量的本质其实就是一把计数器!!而我们对计数器有2个操作,一个是增加计数器的值,一个是减少计数器的值。

而减少值的操作我们称之为P操作。增加值的操作我们称之为V操作

而信号量的P,V操作都是原子的!当信号量计数为0时执行P操作。那么该线程就会进入等待,直到信号量计数不为0才会继续唤醒。而此时另一个线程就可以对信号量进行V操作,让计数器++,从而唤醒之前进入等待的线程。

从本质上说,P操作就是从临界资源拿数据,V操作就是往临界资源放数据!!而又因为P,V操作都是原子的。所以整个过程是线程安全的!!

在这里插入图片描述

POSIX信号量的接口

创建信号量

sem_t x //sem_t 变量名,创建信号量

信号量初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);
参数一: 信号量的地址
参数二: 选项,0为线程信号量,非0为进程信号量
参数三: 信号量的值(信号量本身是一把计数器)
返回值: 成功返回0,失败返回 -1 或者 错误码

信号量的销毁

int sem_destroy(sem_t *sem);
参数: 要销毁的信号量的地址
返回值: 成功返回0,失败返回 -1 或者 错误码

信号量的P操作

信号量的P操作是对信号量的计数进行-- ,当-到0时线程会挂起等待。直到非0后再继续往后执行。

int sem_wait(sem_t *sem);

信号量的V操作

信号量的V操作是对信号量的计数进行++。

int sem_post(sem_t *sem);

查看信号量计数的值

 int sem_getvalue(sem_t *sem, int *sval);
第一个参数传信号量的地址
第二个参数是一个输出型参数,返回信号量的值

信号量的简单运用

因为信号量是一把计数器!!所以我们要有线程对计数器进行–的同时,也必须有线程对计数器进行++。否则一旦计数-到0时。那么就线程就会进入等待,直到 计数器 > 0 时才会被唤醒。

而我们可以对 P,V操作的频率进行控制,当 P操作快,V操作慢时。P操作最终会等待V操作。当V操作快,P操作慢时。那计数器会越涨越多。 所以一般都是 P 操作快,V操作慢。而P是拿数据对应的是消费者,V是放数据对应的生产者。

以下图是 P操作快,V操作慢的情况。所以当信号量计数为0时,P操作会等待V操作。

在这里插入图片描述

那么接下来我们来设计一个抢票程序。程序流程大概如下:

首先,创建一个信号量sem_tickets,并初始化计数器为500。说明一开始上了500张票。

随后创建4个线程,1个线程生产票,3个线程进行抢票。 为了方便给线程命名,我们把信号量和线程名封装到一个ThreadData类中。

然后观察程序。

代码:

#include <pthread.h>
#include <string>
#include <semaphore.h>
#include <unistd.h>int tickets = 500;  //初始信号量的值
//线程数量
#define Thread_num 4//存储线程的线程名和信号量
class ThreadData
{
public:ThreadData(const std::string& name,sem_t* sem_tickets) : _name(name),_sem_tickets(sem_tickets){}std::string _name; sem_t *_sem_tickets;
};//抢票线程执行逻辑
void* BuyTicket(void* args)
{ThreadData* td = (ThreadData*)args;while(true){usleep(1000); //抢票还是不要抢太快,1000微秒抢一张比较好sem_wait(td->_sem_tickets); // P 操作 ,对计数器进行--int x;sem_getvalue(td->_sem_tickets,&x);printf("%s 抢了一张票,还剩下 : %d\n",td->_name.c_str(),x);}
}//放票线程执行逻辑
void* PutTicket(void* args)
{ThreadData* td = (ThreadData*)args;while(true){sleep(1); //每隔1秒进行一次V操作sem_post(td->_sem_tickets); // V 操作 ,对票数++int x; //接收getvalue函数,获取信号量的计数器sem_getvalue(td->_sem_tickets,&x);printf("%s 放了一张票,票数 : %d\n",td->_name.c_str(),x);}
}int main()
{pthread_t tids[Thread_num]; //开四个线程,三个线程抢票,一个线程放票sem_t sem_tickets; //创建信号量sem_init(&sem_tickets,0,tickets); //初始化信号量for(int i = 0 ; i < Thread_num ; i ++){ if(i == 0) //第0个线程放票,其他线程抢票{//创建放票线程std::string name = "放票 thread " + std::to_string(i + 1);ThreadData* td = new ThreadData(name,&sem_tickets);pthread_create(tids+i, nullptr,PutTicket,(void*)td);}else{//创建抢票线程std::string name = "抢票 thread " + std::to_string(i + 1);ThreadData* td = new ThreadData(name,&sem_tickets);pthread_create(tids+i, nullptr,BuyTicket,(void*)td);}}//线程等待for(int i = 0 ; i < Thread_num; i ++){pthread_join(tids[i],nullptr);}//销毁信号量sem_destroy(&sem_tickets);return 0 ;
}

运行结果:

在这里插入图片描述

我们可以看到,一开始的500张票转眼间就被抢光了。然后每生产一张票,抢一张票,这个过程是同步的。

信号量 VS 条件变量

信号量和条件变量的区别在哪呢??

本质区别就是信号量知道临界资源的情况!!

而条件变量并不知道临界资源的情况!!所以使用条件变量时,必须要先对临界资源做检测!!

而信号量,P,V操作结束之后就确保一定能访问临界资源!!

条件变量要先对临界资源做检测才能访问临界资源!!

所以,如果是信号量的时候,加锁加在哪。如果是条件变量的时候,加锁加在哪里?

因为信号量知道临界资源的情况,所以P,V操作之后是一定可以访问临界资源的,而PV操作本身又是原子的。所以可以加锁加在P,V操作之前。当然也可以把P,V操作放在加锁和解锁之间,但这样并不建议。因为抢信号量本身是一个占坑的过程,本身就是一种预定机制,并且这个占坑的过程还是原子的。如果把占坑的这个过程放在加锁和解锁之间,就相当于一个有十个坑的厕所。但必须一个一个的去上。

再打个比方:

在电影院中,是在大厅买票后,再进入小门排队进去看电影好呢。还是直接进入小门排队买票再进去好呢?毫无疑问,肯定是大厅买票后再排队好,因为在大厅卖票速度很快,而排队就会轻松很多。而如果进小门排队买票,那么原本可以在好几个地方都可以买票,也就是说可以几个人同时买票。而现在只能在一个地方买票,只能一个人一个人买票,买票效率大大降低。

所以信号量放在加锁前,就是先在大厅买票,之后加锁就是进入小门排队。这样可以同时多个人买票后再排队

放在加锁后就是直接进入小门买票,这样就只能同时一个人买票。

信号量放在加锁前,提升效率!

而条件变量并不知道临界资源的情况,所以要对临界资源做检测。而对临界资源做检测就一定要访问临界资源!!而这个时候就必须要加锁。所以条件变量必须要在加锁和解锁之间!!

如果条件变量wait不放在加锁和解锁之间,那是很容易造成死锁的。放在加锁之前被唤醒后必定死锁!!因为wait在等待时会释放锁,而此时你根本没有锁,所以释放了个寂寞。可是当在条件变量wait中的线程被唤醒时,可是会重新获得锁的,而你此时又去加锁。那恭喜你!死锁在等着你!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/191686.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink(七)【输出算子(Sink)】

前言 今天是我写博客的第 200 篇&#xff0c;恍惚间两年过去了&#xff0c;现在已经是大三的学长了。仍然记得两年前第一次写博客的时候&#xff0c;当时学的应该是 Java 语言&#xff0c;菜的一批&#xff0c;写了就删&#xff0c;怕被人看到丢脸。当时就想着自己一年之后&…

代码随想录算法训练营第五十五天|392. 判断子序列、115. 不同的子序列

第九章 动态规划 part15 392. 判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace&q…

使用 Filebeat+Easysearch+Console 打造日志管理平台

近年来&#xff0c;日志管理平台越来越流行。使用日志管理平台可以实时地、统一地、方便地管理和查看日志&#xff0c;挖掘日志数据价值&#xff0c;驱动运维、运营&#xff0c;提升服务管理效率。 方案架构 Beats 是轻量级采集器&#xff0c;包括 Filebeat、Metricbeat 等。E…

Nginx安装配置与SSL证书安装部署

一、Nginx Nginx是一款高性能的开源Web服务器和反向代理服务器&#xff0c;被广泛用于构建现代化的Web应用和提供静态内容。 nginx官网 这里下载nginx-1.24.0-zip Nginx是一款高性能的开源Web服务器和反向代理服务器&#xff0c;被广泛用于构建现代化的Web应用和提供静态内…

“可信区块链运行监测服务平台TBM发展研讨会”将于11月23日在北京召开

为推动区块链治理与创新&#xff0c;积极推进信任科技生态体系建设&#xff0c;中国信息通信研究院、中国移动设计院联合区块链服务网络&#xff08;BSN&#xff09;发展联盟共同发起建立了可信区块链运行监测服务平台&#xff08;TBM&#xff09;。 TBM平台通过对区块链系统的…

​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第20章 系统架构设计师论文写作要点&#xff08;P717~728&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图

C语言基本算法----冒泡排序

原理 冒泡排序就是对一个存放N个数据的数组进行N次扫描&#xff0c;每次把最小或者最大的那个元素放到数组的最后&#xff0c;达到排序的目的。 原理图解 冒泡排序过程分析 冒泡排序的执行过程 冒泡排序总结 在此感谢 冒泡排序法_哔哩哔哩_bilibili 这篇blog是对这位up此视…

管理类联考——逻辑——知识+记忆篇——综合推理——考点+记忆

文章目录 整体目录大纲法汇总分类法记忆宫殿法绘图记忆法 考点记忆/考点汇总——按大纲 局部数字编码法归类记忆法重点记忆法歌决记忆法谐音记忆法理解记忆法比较记忆法 本篇思路&#xff1a;根据各方的资料&#xff0c;比如名师的资料&#xff0c;按大纲或者其他方式&#xff…

Consistency Models 阅读笔记

Diffusion models需要多步迭代采样才能生成一张图片&#xff0c;这导致生成速度很慢。Consistency models的提出是为了加速生成过程。 Consistency models可以直接一步采样就生成图片&#xff0c;但是也允许进行多步采样来提高生成的质量。 Consistency models可以从预训练的扩…

【Python数学练习1】

一、题目 中文描述&#xff1a; 给出正整数N&#xff0c;输出满足条件的数对(a,b)的个数&#xff0c;满足gcd(a,b)b, a,b < n 数学描述&#xff1a; 二、解法 解法1&#xff1a; 对应Python代码&#xff1a; def num_fact(n):num 0for i in range(1, n 1):if n % i …

腾讯云服务器新用户优惠怎么领?附腾讯云新用户优惠领取链接

大家好&#xff0c;今天我们来聊聊腾讯云服务器的优惠活动&#xff01;如果你是腾讯云的新用户&#xff0c;那么你一定不能错过这个机会&#xff01; 首先&#xff0c;新用户可以领取双十一9999代金券&#xff0c;这可是一大笔钱啊&#xff01;而且&#xff0c;你还可以另外再…

C++17中std::variant的使用

可变参数模板类std::variant表示类型安全联合体(type-safe union)。std::variant的实例在任何给定时间要么保存其替代类型之一的值&#xff0c;要么在错误的情况下无值。 与union一样&#xff0c;如果std::variant保存某个对象类型T的值&#xff0c;则T的对象表示形式将直…