linux下的信号量PV操作进阶之路
一.同步和互斥机制
信号量
互斥锁
同步:指多个任务按照约定的先后次序相互配合来完成一件事情. 比如读线程等待写线程写完之后再去读.
二.信号量-P/V操作
P(s)含义:
if(信号量>0)
{
申请资源的任务运行;
信号量--;
}
else
{申请资源的任务阻塞}
V(S)含义:
信号量++;
if(有任务在等待资源)
{
唤醒等待的任务,让其继续运行.
}
三.Posix信号量:
无名信号量(基于内存的信号量):多用于同一进程的多个线程之间.
有名信号量:既可用于线程之间,也可用于进程之间.
四.无名信号量常用函数:
#include<semaphore>
int sem_init(sem_t *sem, int pshared, unsigned int val); 初始化
pshared: 0-线程间 1-进程间
val:信号量初值.
int sem_wait(sem_t *sem); P操作(可以这样理解,等下,我看看有没有资源)
int sem_post(sem_t *sem); V操作
五.结合例子讲解
题目: 两个线程同步读写缓冲区(生产者/消费者问题,这里的读写是相对于缓冲区来说的)
否定之否定的实现道路:
1.错误实现1(自己一开始按照互斥思想的错误实现)
在同一个线程中开始位置写上了sem_wait(&s),结束位置写上了sem_post(&s).这根本就不是信号量的用法,哈哈哈.
2.错误实现2(把P操作放错位置)
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h> char buff[];
sem_t s; //用两个子线程来实现
void *ReadTask(void *arg)
{
while()
{
if(strcmp(buff, "quit", ) == )
{
printf("hehe, 遇到quit,退出\n");
break;
}
else
{
sem_wait(&s);
printf("所读内容:%s\n", buff); //把数据从缓存区中读出
}
}
pthread_exit("ReadTask End!");
} void *WriteTask(void *arg)
{
do
{
scanf("%s", &buff); //把数据写到缓存区中
sem_post(&s);
}while(strcmp(buff, "quit", ) != );
pthread_exit("WriteTask End!");
} int main()
{
if (sem_init(&s, , ) < 0) //此处一开始将可用资源设为0.
{
perror("sem_init");
exit(-1); //直接退出线程
}
pthread_t t_read;
pthread_t t_write; //即使这里先创建的t_read,也不代表t_read先执行.它们的执行顺序不固定的.
int rc1 = pthread_create(&t_read, NULL, ReadTask, NULL);
if(rc1<0)
{
perror("pthread_create t_read");
exit(-1);
}
int rc2 = pthread_create(&t_write, NULL, WriteTask, NULL);
if(rc2<0)
{
perror("pthread_create t_write");
exit(-1);
}
pthread_join(t_read, NULL);
pthread_join(t_write, NULL); return ;
}
这里本来预想的结果是:当我输入"quit"后,直接执行if分支中的并退出线程.最后发现不是这个回事.发现还会进入到else的分支里,感觉很是费解.
后来gdb调试,发现问题出现在了sem_wait这一行.
当我敲入"quit"之前,读线程就已经执行到了sem_wait这里(因为buff符合else条件),只是因为信号量为0,所以阻塞在这里.等我敲入"quit"后,写线程将其写到buff中,然后唤醒了读线程,才有了下面的结果.
quit
所读内容:quit
hehe, 遇到quit,退出
3.对上面进行修改:
只需把sem_wait放在线程循环一开始的地方.
void *ReadTask(void *arg)
{
while()
{
sem_wait(&s);
if(strcmp(buff, "quit", ) == )
{
printf("hehe, 遇到quit,退出\n");
break;
}
else
{
printf("所读内容:%s\n", buff); //把数据从缓存区中读出
}
}
pthread_exit("ReadTask End!");
}
4.用主线程和子线程来实现:
当输入"quit"时,主线程循环结束,主线程return 0,子线程也跟着结束了.
为何要先初始化信号量,再创建线程呢?
如果先创建线程的话,主线程和子线程谁先运行是不确定的,这带来什么影响呢? 如果把信号量初始化放在主线程中并且在子线程创建之后,如果子线程中内容先执行并且对信号量进行了操作,就会出现问题,因为此时信号量还没有初始化.
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h> char buff[];
sem_t s; //用一个子线程
void *ReadTask(void *arg)
{
while()
{
sem_wait(&s);
printf("所读内容:%s\n", buff); //把数据从缓存区中读出
}
pthread_exit("ReadTask End!");
} int main()
{
if(sem_init(&s, , )) //此处一开始将可用资源设为0.
{
perror("sem_init");
exit(-1);
}
pthread_t t_read; //即使这里先创建的t_read,也不代表t_read先执行.它们的执行顺序不固定的.
int rc1 = pthread_create(&t_read, NULL, ReadTask, NULL);
if(rc1 < 0)
{
perror("pthread_create t_read");
exit(-1);
}
do
{
scanf("%s", &buff); //把数据写到缓存区中
sem_post(&s);
}while(strcmp(buff, "quit", ) != ); return ;
}
5.以上几种情况使用一个信号量存在的问题,不是严格意义上的同步(严格意义上:写的时候,不要读;读的时候不要写):
上面例子中的信号量实际上是针对读线程的,保证缓冲区中有数据时才可以去读.但是并没有针对写线程,因为需要保证在读的时候,不要往缓冲区中去写入.不然会造成读取异常的问题.
举例:
上述的例子中,如果读线程花的时间比较长,而写线程一直往里面写.就会导致前面的内容被后面的给覆盖掉,而读线程只是读到了后面的内容.
void *ReadTask(void *arg)
{
while()
{
sem_wait(&s);
sleep(); //来模拟读的过程时间长
printf("所读内容:%s\n", buff); //把数据从缓存区中读出
}
pthread_exit("ReadTask End!");
}
可能的结果是
aaa
bbb
ccc
所读内容:ccc
所读内容:ccc
所读内容:ccc
quit
6.用两个信号量来实现同步过程:
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h> char buff[];
sem_t sem_r;
sem_t sem_w; //用一个子线程
void *ReadTask(void *arg)
{
while()
{
sem_wait(&sem_r); //判断sem_r是否大于0,是的话,就去--,申请资源的任务运行;否的话就阻塞
sleep();
printf("所读内容:%s\n", buff); //把数据从缓存区中读出
sem_post(&sem_w);//读的信号量++,如果有读操作处于阻塞,就把它给唤醒.
}
pthread_exit("ReadTask End!");
} int main()
{
if(sem_init(&sem_w, , )<) //写信号量一开始为1
{
perror("sem_init sem_w");
exit(-);
}
if(sem_init(&sem_r, , )<) //读信号量一开始为0
{
perror("sem_init sem_r");
exit(-);
}
pthread_t t_read; //即使这里先创建的t_read,也不代表t_read先执行.它们的执行顺序不固定的.
if(pthread_create(&t_read, NULL, ReadTask, NULL)<)
{
error("pthread_create t_read");
exit(-);
} do
{
sem_wait(&sem_w);
scanf("%s", &buff); //把数据写到缓存区中
sem_post(&sem_r);
}while(strcmp(buff, "quit", ) != );
return ;
}
运行结果如下: 这个时候来不及处理的字符串会阻塞在sem_wait(&sem_w);它没有进到缓冲区中会在输入窗中排队.
a
b
c
d
所读内容:a
所读内容:b
所读内容:c
所读内容:d
之前对PV操作只是感性的认识,今天结合这个教程学习了很多,谢谢这个老师了.
视频链接:https://www.bilibili.com/video/BV1Fs411M7d5?p=2
linux下的信号量PV操作进阶之路的更多相关文章
- linux 下的信号量参数
linux 下的信号量参数 转载自:http://blog.itpub.net/26110315/viewspace-718306/ 信号量是一种锁机制用于协调进程之间互斥的访问临界资源.以确保某种共 ...
- linux下mysql数据库的操作
本文主要针对linux下mysql数据库的安装,以及数据库的创建和简单的数据库操作进行说明. ①.Mysql数据库的安装: 数据库的安装分为源码安装和rpm安装. 当然对于老手来说需要进行一些自定义的 ...
- Linux下mysql的常用操作
Linux下mysql的常用操作: 显示数据库 show databases; 选择数据库 use 数据库名; 显示数据库中的表 show tables; 显示数据表的结构 describe 表名; ...
- Linux下MySQL的简单操作
Linux下MySQL的简单操作 更改mysql数据库root的密码 首次进入数据库是不用密码的: [root@localhost ~]# /usr/local/mysql/bin/mysql -ur ...
- Linux下IPC中的信号量PV操作
代码如下所示,两边对照查看程序!(左图为先运行进程 右图为后运行进程) 运行的效果就是:当左边的进程检测到EOF,释放资源V操作之后,右边的进程会迅速的执行对应的printf的操作! 所有代码文 ...
- Linux下用信号量实现对共享内存的访问保护
转自:http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html 最近一直在研究多进程间通过共享内存来实现通信的事情,以便高 ...
- linux下sendmail邮件系统安装操作记录
电子邮件系统的组成:1)邮件用户代理(Mail User Agent , MUA),MUA是一个邮件系统的客户端程序,它提供了阅读,发送和接受电子邮件的用户接口. 最常用的 MUA 有: linux ...
- linux下vi或vim操作Found a swap file by the name的原因及解决方法
在linux下用vi或vim打开Test.java文件时 [root@localhost tmp]# vi Test.java出现了如下信息: E325: ATTENTION Found a s ...
- linux下Redis以及c++操作
使用不同的语言,redis支持不同的编程语言,但是调用了不同的redis包,例如: java对应jedis: php对应phpredis: C++对应的则是hredis. 安装Redis 上篇博客已经 ...
随机推荐
- drf 权限认证
目录 复习 前期准备 三大认证简介 AbstracUser源码分析 自定义User下的权限六表 models.py 到settings.py中注册 注意点: 执行数据迁移的俩条命令 创建超级用户 t_ ...
- 【codeforces】Codeforces Round #612 (Div. 2) C. Garland——DP
题目链接 贪心模拟了半天,最后放弃了 题意 给你一串从1−n1-n1−n的序列,其中部分未知(表示为0),补全序列使得相邻数值奇偶性相反的数量最少 相邻数值的奇偶性相反:两个相邻的两个数值,其中一个为 ...
- Hive数据倾斜的原因及主要解决方法
数据倾斜产生的原因 数据倾斜的原因很大部分是join倾斜和聚合倾斜两大类 Hive倾斜之group by聚合倾斜 原因: 分组的维度过少,每个维度的值过多,导致处理某值的reduce耗时很久: 对一些 ...
- [转载]float 和 double 的一二三点事
文章转载于博客园作者jillzhang,原文链接http://jillzhang.cnblogs.com/ .文章为jillzhang原创,转载条件请看原文链接. 关于浮点数精度和范围,请看末尾. C ...
- Fedora20在神州战神K650D1安装过程,使用netinstall和Dvd.iso镜像安装。
最近新买一笔记本,神州战神K650D-i5 D1.仍旧安装双系统,WIndows7+Fedora20.磁盘分区是这样的: 第一主分区 /boot ext4 20G 第二主分区 / ext4 70G 第 ...
- Kubernets中获取客户端真实IP总结
1. 导言 绝大多数业务场景都是需要知道客户端IP的 在k8s中运行的业务项目,如何获取到客户端真实IP? 本文总结了通行的2种方式 要答案的直接看方式一.方式二和总结 SEO 关键字 nginx i ...
- python基础学习-字符串常见操作
字符串常见操作 索引 s = "abcdefg" # 字符串数据,切片后取出的数据都是字符串类型 # 从左至右取值:从0开始 # 从右向左取值:从-1开始 print(" ...
- java web利用mvc结构实现简单聊天室功能
简单聊天室采用各种内部对象不适用数据库实现. 一个聊天室要实现的基本功能是: 1.用户登录进入聊天室, 2.用户发言 3.用户可以看见别人发言 刚才算是简单的需求分析了,现在就应该是进 ...
- M - 诡异的楼梯 HDU - 1180(BFS + 在某个点等待一下 / 重复走该点)
M - 诡异的楼梯 HDU - 1180 Hogwarts正式开学以后,Harry发现在Hogwarts里,某些楼梯并不是静止不动的,相反,他们每隔一分钟就变动一次方向. 比如下面的例子里,一开始楼梯 ...
- A换算时间(只想开学)HDU 6556
题目链接 思路如下 把时间转化为 24小时制下进行考虑,首先我们要明白(在24小时制下):12 点表示是下午PM ,而 24点表示的是明天的 0点(12小时制下),这两个地方需要特殊考虑 题解如下 # ...