• 第三季-第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 ",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 ",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

    这就是我们要的结果。

  • 相关阅读:
    spring boot SpringApplication.run 执行过程
    算法 计算四则运算字符串结果
    算法 RingBuffer
    java BigDecimal 四舍五入
    算法 常用函数和近似
    java 多线程执行
    Java 三个线程依次输出ABC
    Java interrupt 中断
    java 垃圾收集器与内存分配策略
    软件项目与软件产品的区别
  • 原文地址:https://www.cnblogs.com/free-1122/p/11351438.html
Copyright © 2020-2023  润新知