一.同步和互斥机制

信号量

互斥锁

同步:指多个任务按照约定的先后次序相互配合来完成一件事情. 比如读线程等待写线程写完之后再去读.

二.信号量-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操作进阶之路的更多相关文章

  1. linux 下的信号量参数

    linux 下的信号量参数 转载自:http://blog.itpub.net/26110315/viewspace-718306/ 信号量是一种锁机制用于协调进程之间互斥的访问临界资源.以确保某种共 ...

  2. linux下mysql数据库的操作

    本文主要针对linux下mysql数据库的安装,以及数据库的创建和简单的数据库操作进行说明. ①.Mysql数据库的安装: 数据库的安装分为源码安装和rpm安装. 当然对于老手来说需要进行一些自定义的 ...

  3. Linux下mysql的常用操作

    Linux下mysql的常用操作: 显示数据库 show databases; 选择数据库 use 数据库名; 显示数据库中的表 show tables; 显示数据表的结构 describe 表名; ...

  4. Linux下MySQL的简单操作

    Linux下MySQL的简单操作 更改mysql数据库root的密码 首次进入数据库是不用密码的: [root@localhost ~]# /usr/local/mysql/bin/mysql -ur ...

  5. Linux下IPC中的信号量PV操作

    代码如下所示,两边对照查看程序!(左图为先运行进程 右图为后运行进程)    运行的效果就是:当左边的进程检测到EOF,释放资源V操作之后,右边的进程会迅速的执行对应的printf的操作! 所有代码文 ...

  6. Linux下用信号量实现对共享内存的访问保护

    转自:http://www.cppblog.com/zjl-1026-2001/archive/2010/03/03/108768.html 最近一直在研究多进程间通过共享内存来实现通信的事情,以便高 ...

  7. linux下sendmail邮件系统安装操作记录

    电子邮件系统的组成:1)邮件用户代理(Mail User Agent , MUA),MUA是一个邮件系统的客户端程序,它提供了阅读,发送和接受电子邮件的用户接口. 最常用的 MUA 有: linux ...

  8. 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 ...

  9. linux下Redis以及c++操作

    使用不同的语言,redis支持不同的编程语言,但是调用了不同的redis包,例如: java对应jedis: php对应phpredis: C++对应的则是hredis. 安装Redis 上篇博客已经 ...

随机推荐

  1. mysql存储过程简单用法

    show procedure status 查看所有存储过程 <!--  简单存储过程  --> 先将结束符改成// delimiter // create procedure query ...

  2. PTA 创建计算机类

    6-5创建计算机 (10分) 定义一个简单的Computer类,有数据成员芯片(cpu).内存(ram).光驱(cdrom)等等,有两个公有成员函数run.stop.cpu为CPU类的一个对象,ram ...

  3. matplotlib.pyplot库函数关于坐标轴显示的支持

    matplotlib.pyplot库函数关于坐标轴显示的支持 https://blog.csdn.net/Stark_595/article/details/80787005?depth_1-utm_ ...

  4. 粒子群优化算法(PSO)之基于离散化的特征选择(FS)(二)

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识! 作者:Geppetto 前面我们介绍了特征选择(Feature S ...

  5. HDU - 2444 二分图最大匹配 之 判断二分图+匈牙利算法

    题意:第一行给出数字n个学生,m条关系,关系表示a与b认识,判断给定数据是否可以构成二分图,如果可以,要两个互相认识的人住一个房间,问最大匹配数(也就是房间需要的最小数量) 思路:要看是否可以构成二分 ...

  6. HOOK相关原理与例子

    消息HOOK 原理: 1. 用户输入消息,消息被放到系统消息队列. 2. 程序发生了某些需要获取输入的事件,就从系统消息队列拿出消息放到程序消息队列中. 3. 应用程序检测到有新的消息进入到程序消息队 ...

  7. vscode vue 模版生成,vue 一键生成

    vscode vue 模版 继上篇文章(vue 格式化),顺便记录下 vue 模版生成.图片就不在贴了,如果有找不到 vscode 插件商店的可以访问上篇文章. 一.安装 VueHelper 在 vs ...

  8. 图的广度优先遍历(bfs)

    广度优先遍历: 1.将起点s 放入队列Q(访问) 2.只要Q不为空,就循环执行下列处理 (1)从Q取出顶点u 进行访问(访问结束) (2)将与u 相邻的未访问顶点v 放入Q, 同时将d[v]更新为d[ ...

  9. Java中如何调用静态方法

    Java中如何调用静态方法: 1.如果想要调用的静态方法在本类中,可直接使用方法名调用 2.调用其他类的静态方法,可使用类名.方法名调用 关于静态方法能被什么调用 1.实例方法 2.静态发放

  10. flask中 多对多的关系 主从表之间的的增删改查

    # 角色表模型class Role(db.Model): r_id = db.Column(db.Integer, primary_key=True) r_name = db.Column(db.St ...