第三季-第16课-信号量互斥编程

第16课-信号量互斥编程

 

16.1 公示栏问题(问题引入)

1. 问题描述

这里面我们举一个小例子。在一个班级里就有一个公示栏,A同学想写“数学课考试”,B同学想写“英语课取消”。但是有一个时间,A同学只写下了“数学课”三个字,没来得及写后面的内容就出去了,但是这个时候B同学来写下了“英语课取消”。这样让同学们看来就成了“数学课英语课取消”,给班级的其他同学造成了歧义。

这也就是我们说的同时访问一个资源,造成了,数据的混乱。若是有多个进程同时访问一个资源,同样会造成这个问题。

2. 问题程序化

A同学用进程A代替,B同学用进程B代替,公示栏用文件来代替。我们在先关课程的文件夹里面建立student1.c和student2.c。

注:这里加入一个操作---ctil+shift+t可以完成复制一个一样的命令栏窗口。

stduent1程序:

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

void main()

{

         int fd = 0;

         //0.打开文件

         fd = open("./board.txt",O_RDWR|O_APPEND,0777);

         //1.向公告板文件里面写入“数学课”

         write(fd,"math class",11);

         //2.暂停休息

sleep(5);

         //3.向公告板文件里写入“取消”

         write(fd,"is cancel",11);

         close(fd);

}

stduent2程序:

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

void main()

{

         int fd = 0;

         //0.打开文件

         fd = open("./board.txt",O_RDWR|O_APPEND,0777);

         //1.向公告板文件里面写入“英语课考试”

         write(fd,"English exam",20);

         //2.关闭文件

         close(fd);

}

运行结果:我们开起两个中断在一个终端中运行:./stduent1,5s内在另一个终端中运行./stduent2。我们查看提前创建好的board.txt文件显示为:math class English exam   


;is cancel 

3. 信号量概念

为了能够是一个程序不会出现访问混乱的情况,我们可以说白一点,像厕所一样,有人进入就有标识牌显示有人,即使时间很长。从而达到了互斥的访问。这一个概念就是信号量。

l  概念:信号量(又名:信号灯)与其他进程间通讯方式不大相同,主要用途是保护临界资源(进程互斥)。进程可以根据它判定是否能够访问某些共享资源。除了用于控制外,还可以用于进程同步。

l  分类:

二值信号灯:信号灯的值只能取0或1;

计数信号灯:信号灯的值可以取任意非负值。        

l  信号量使用流程:

1)         打开信号量,获得标示符

2)         利用标示符,进行一系列操作

16.2 函数学习

1. 创建/打开信号量

(1)函数名

semget

(2)函数原型

int semget(key_t key, int nsems, int semflg);

(3)函数功能

获取信号量集合(一个信号或者多个信号)的标示符(get a semaphore set identifier),当key所指的信号量不存在,并且semflg里面包含了IPC_CREAT,就会创建这个信号量集合。

(4)所属头文件

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

(5)返回值

成功:返回信号量集合的标示符;

失败:-1.

(6)参数说明

l  什么是“键值”:(文件创建有文件名,使用文件的时候有文件的fd(id号);ipc对象创建的时候有键值(一个数字),使用的时候有标示符)对于一个文件,我们只要找到文件名,我们就能找到文件id(fd),打开文件。对于一个信号量(ipc对象)无论使用还是没有使用都是具有键值的,我们要知道他的“键值”我们就能打开它的标示符。标示符是打开这个ipc对象才会有的,键值是无论打开与没打开都有的。无论是A进程还是B进程,我们要找到它的信号量,我们都要知道它的“键值”。在我们创建信号量的时候(创建之后就会产生),我们就为它指定了键值。键值如何选取呢(在ipc对象创建的时候)?这里我们给出两个方法:

(1)任意指定一个数

缺点:这个数已经被别的IPC对象(消息队列,共享内存)所使用了,在于新创建的信

量关联时就会失败。

(2)构造一个尽量不会被别的IPC对象用到的数的方法:使用key_t ftok(char *fname,int id)

这个函数有两个参数,第一个是文件名,第二个是项目id。根据这个文件名,我们可以得到一个数字,这个数字,是文件在内核里面保存文件信息的一个文字。将这个数字再和项目id组合起来,就形成我们所要的键值了。这个键值的数字的产生:内核中保存该文件名的结构中的一个数字 + 项目id,这样形成的一个数字。也就是我们知道文件名和项目id可以得到ipc对象的键值,知道ipc对象的键值可以得到ipc对象的标示符。

         每个进程的标示符有唯一的键值与之对应。

key:打开的信号量对应的键值。

semflg:标志,可以取IPC_CREAT,当key所指的信号量不存在,并且semflg里面包含了IPC_CREAT,就会创建这个信号量集合。

nsems:创建的这个信号量集合里面包含的信号量数目,是个数字。

2. 操作信号量

(1)函数名

semop

(2)函数原型

int semop(int semid, struct sembuf *sops, unsigned nsopos);

(3)函数功能

操作信号量集合里面的信号量。

(4)所属头文件

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/sem.h>

(5)返回值

成功:0;

失败:-1.

(6)参数说明

semid:要操作的信号量集合(可以一个可以多个)的标示符。

sembuf:它包含下面的集合,

unsigned short sem_num;  /*semaphore number*/ 操作的信号量集合的编号

short        sem_op;   /*semphore operation*/ 正数表示释放(+1),负数表示获取(-1),不成功导致程序等待

short        sem_flg;   /*operation flags*/

nsops:一共操作了多少的信号量

sops:对信号量执行了什么样的操作,一个指针对应一个操作,若是有三个操作,就是组数为3的数组。

16.3 综合实例编程

         A在使用黑板前获取信号量,然后开始使用黑板。使用黑板的时候,信号量的值应该是0。使用完了黑板释放信号量,信号量变成1。由于是A同学率先使用这个信号量的,所以要由A同学来创建这个信号量。

         B同学在使用的时候在先获取信号量,能够获取的到,就直接使用。若在获取的过程中,发现这个信号量被A获取了,就等待直到A同学使用完毕。B同学使用完信号量,释放信号量。

stduent1.c

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<sys/ipc.h>

#include<sys/sem.h>

void main()

{

         int fd = 0;

         int semid;

         key_t key;

         struct sembuf sops;

         //创建信号量的键值

         key = ftok("/home",1); //使用文件名,和数字的方式,可以任意组合。

         //创建并打开信号量

semid = semget(key,1,IPC_CREAT);//在系统中没有这个信号量,用参数IPC_CREAT;//就用一个信号量,使用数字1

         //0.打开文件

         fd = open("./board.txt",O_RDWR|O_APPEND,0777);

         //1.1 获取信号量

sops.sem_num = 0;  //我们的信号量集合里面只有一个信号,我们操作的信号的序//号一定是0

         sops.sem_op = -1;   //-1表示获取这个信号量

semop(semid,&sops,1); //操作一个信号量,第三个参数用1;第二个参数表示执行什么样的操作

         //1.向公告板文件里面写入“数学课”

         write(fd,"math class",11);

         //2.暂停休息

         sleep(10);

         //3.向公告板文件里写入“取消”

         write(fd,"is cancel",11);

         //释放信号量

         sops.sem_num = 0; //第一个信号量

         sops.sem_op = 1;  //释放信号量,+1的操作

         semop(semid,&sops,1);

         close(fd);

}

Stduent2.c

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<sys/ipc.h>

#include<sys/sem.h>

void main()

{

         int fd = 0;

         int semid;

         key_t key;

         struct sembuf sops;

         //创建信号量的键值

         key = ftok("/home",1); //保证两个学生操同一个信号量,键值的组成是一样的。

         //打开信号量集合

         semid = semget(key,1,IPC_CREAT);

         //0.打开文件

         fd = open("./board.txt",O_RDWR|O_APPEND,0777);

         //获取信号量

         sops.sem_num = 0;

         sops.sem_op = -1;

         semop(semid,&sops,1);

         //1.向公告板文件里面写入“英语课考试”

         write(fd,"English exam",12);

         //释放信号量

         sops.sem_num = 0;

         sops.sem_op = 1;

         semop(semid,&sops,1);

         //2.关闭文件

         close(fd);

}

运行结果:当我们在两个终端中分别运行./student1和./student2时,我们按道理应该看到的情况是,第一个程序在等待,第二个程序也在等待,直到第一个程序完成。并且写入的数据不混乱。但是,我们这两种期望都没看到。运行B程序,瞬间就结束了;写入的数据也不对。这里我们忽略了一个东西,我们前面一直在做一个假设,就是信号量的初始值是1。

实际的信号量的值不一定是1,可能是4、5、6等其他值。

这里我们引入另一个函数:semctl

函数原型

int semctl(int semid , int sennum, int cmd,.../*union semum arg*/);  //有10种,我们用到GETVAL这个命令

下面在student.c加入这两行命令:    int ret;

     ret = semctl(semid,1,GETVAL);

发现它的初始值是:-1;

下面是更改后的程序

student1.c

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<sys/ipc.h>

#include<sys/sem.h>

void main()

{

         int fd = 0;

         int semid;

         key_t key;

         int ret;

         struct sembuf sops;

         //创建信号量的键值

         key = ftok("/home",1);

         //创建并打开信号量集合

         semid = semget(key,1,IPC_CREAT);

         ret = semctl(semid,0,SETVAL,1);  //给信号量赋初值:1

         printf("init value of sem is %d\n",ret);

         //0.打开文件

         fd = open("./board.txt",O_RDWR|O_APPEND,0777);

         //1.1 获取信号量

         sops.sem_num = 0;

         sops.sem_op = -1;

         semop(semid,&sops,1);

         //1.向公告板文件里面写入“数学课”

         write(fd,"math class",11);

         //2.暂停休息

         sleep(10);

         //3.向公告板文件里写入“取消”

         write(fd,"is cancel",11);

         //释放信号量

         sops.sem_num = 0;

         sops.sem_op = 1;

         semop(semid,&sops,1);

         close(fd);

}

student2:

#include<unistd.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<stdio.h>

#include<sys/ipc.h>

#include<sys/sem.h>

void main()

{

         int fd = 0;

         int semid;

         key_t key;

         struct sembuf sops;

         int ret;

         //创建信号量的键值

         key = ftok("/home",1);

         //打开信号量集合

         semid = semget(key,1,IPC_CREAT);

         //0.打开文件

         fd = open("./board.txt",O_RDWR|O_APPEND,0777);

         ret = semctl(semid,0,GETVAL); //看看信号量的值是多少

         printf("ret is %d\n",ret);

         //获取信号量

         sops.sem_num = 0;

         sops.sem_op = -1;

         semop(semid,&sops,1);

         //1.向公告板文件里面写入“英语课考试”

         write(fd,"English exam",12);

         //释放信号量

         sops.sem_num = 0;

         sops.sem_op = 1;

         semop(semid,&sops,1);

         //2.关闭文件

         close(fd);

}

运行结果:在两个终端中分别运行./student1和./student2。我们能看到等待的出现。board.txt中的内容是:math class is cancel English exam

这就是我们要的结果。

猜你喜欢

转载自www.cnblogs.com/free-1122/p/11351438.html
今日推荐