一.同步和互斥机制

信号量

互斥锁

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

二.信号量-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. cisco应用

    1.修改VLAN show arp:查看设备的ip地址和mac地址的对应关系,一般查看的是设备ARP缓存 show mac-address-table:查看各端口所学习到的MAC show ip ar ...

  2. 牛客练习赛60 A—F题解(缺E题)

    本蒟蒻这次只过了三题 赛后学习了一下出题人巨佬的标码(码风比我好多了 贴的代码有些是仿出题人)现在将自己的理解写下来与大家分享 A这个题一分析就是每个数字都会与所有数字&一下 (a&a ...

  3. K折-交叉验证

    k-折交叉验证(k-fold crossValidation):在机器学习中,将数据集A分为训练集(training set)B和测试集(test set)C,在样本量不充足的情况下,为了充分利用数据 ...

  4. Building Applications with Force.com and VisualForce (DEV401) (二四):JavaScript in Visualforce

    Dev401-025:Visualforce Pages: JavaScript in Visualforce Module Objectives1.Describe the use of AJAX ...

  5. OpenCV -Python 性能衡量和提升技术 | 十二

    目标 在图像处理中,由于每秒要处理大量操作,因此必须使代码不仅提供正确的解决方案,而且还必须以最快的方式提供.因此,在本章中,你将学习 衡量代码的性能. 一些提高代码性能的技巧. 你将看到以下功能:c ...

  6. 关于getchar的疑惑

    最近做了一道题,我的代码有片段是这样的 while(scanf("%d",&n)) { if(n==0&&getchar()=='\n') break; . ...

  7. [vijos1304]回文数<模拟>

    题目链接:https://vijos.org/p/1304 好久没写博客了,最近一直打不出题,感觉自己是废了,今天做了一道模拟水题,但还是半天没过,后来才发现是忘记考虐10以上的进制是带有字母的,然后 ...

  8. Python Tkinter Grid布局管理器详解

    Grid(网格)布局管理器会将控件放置到一个二维的表格里.主控件被分割成一系列的行和列,表格中的每个单元(cell)都可以放置一个控件. 注意:不要试图在一个主窗口中混合使用pack和grid (1) ...

  9. Vertica的这些事(九)——-vertica存储统计信息

    vertica存储统计信息: 表数量: select count(distinct table_name) FROM tables; 分区表数量: select count(distinct tabl ...

  10. 自执行函数-[javascript]-[语法]

    在看别人的代码的时候,遇到了一种写法,之前没有见过,如下: ![](https://img2018.cnblogs.com/blog/1735896/201912/1735896-2019122114 ...