zoukankan      html  css  js  c++  java
  • 第11章 进程间通信(4)_进程信号量

    3.4 进程信号量

    (1)进程信号量简介

      ①本质上就是共享资源的数目,用来控制对共享资源的访问。

      ②用于进程间的互斥和同步

      ③每种共享资源对应一个信号量,为了便于大量共享资源的操作引入信号量集,可对所有信号量一次性操作。对信号量集中所有操作可以要求全部成功,也可以部分成功。

      ④二元(也叫双态)信号量(信号灯)值为0或1。

      ⑤对信号量做PV操作(P减,V加)

    (2)信号量集属性

     

    (3)创建信号量集

    头文件

    #include <sys/sem.h>

    函数

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

    参数

    key: 用户指定的信号量集键值

    size:信号量集中信号量的个数

    flag:IPC_CREAT、IPC_EXCL等权限组合

    功能

    创建信号量集

    返回值

    成功返回内核中信号量集的标识ID,出错返回-1

    (4)信号量集的控制

    头文件

    #include <sys/sem.h>

    函数

    int semctl(int semid, int semnum, int cmd, …/*union semun arg*/);

    参数

    (1)semid: 信号量集ID

    (2)semnum:0表示对所有信号量操作,信号量编号从0开始。

    (3)union semun{

        int val; //放置获取或设置信号量集中某个信号量的值。

        struct semid_ds* buf;//信号量集属性指针

        unsigned short* array; //放置获取或设置信号量集中所有信号量的值。

          };

    (4)cmd参数:通过参数设定对信号量集要执行的操作

      ①IPC_STAT:获取信号量集的属性       ==> buf

      ②IPC_SET: 设置信号量集的属性        ==> buf

      ③IPC_RMID:删除信号量集                ==> buf

      ④GETVAL:  返回信号量的值               ==> val

      ⑤SETVAL:  设置semnum信号量的值 ==> val

      ⑥GETALL:  获取所有信号量的值         ==> array

      ⑦SETALL:  设置所有信号量的初始值   ==> array

    功能

    控制信号量集

    返回值

    除GETALL以外所有GET命令,semctl函数都返回相应的值。其他命令的返回值为0.成功返回内核中信号量集的标识ID,出错返回-1

    (5)信号量集的操作

    头文件

    #include <sys/sem.h>

    函数

    int semop(int semid, struct sembuf* sops, size_t nsops);

    参数

    (1)semid: 信号量集ID

    (2)sops:sembuf结构体数组指针

    (3)nsops:第2个参数中结构体数组的长度

    (4)struct sembuf{

      unsigned short sem_num; //信号量集中的信号量的编号

      short sem_op; //正数为V操作负数为P操作,0可用于测试资源是否用完。

      short sem_flg;//SEM_UNDO标志,表示在进程结束时,相应的操作将被取消。如果设置了该标志那么在进程没有释放共享资源就退出时,内核将代为释放

    };

    功能

    信号量集的操作

    返回值

    成功返回0,出错返回-1

    备注

    (1)用于信号量集中信号量的加和减操作(PV操作,注意P为减操作,V为加操作)

    (2)可用于进程间的互斥和同步。

    【编程实验】进程信号量实现ATM的互斥

    (1)利用进程信号量的PV操作实现多进程对银行账户操作的互斥。

    (2)I(1):在银行账户上绑定信号量/信号灯(初始值为1)

    (3)任何进程在取款时进行P(1)操作,然后withdraw(),取完后V(1)操作。

    //pv.h

    #ifndef __PV_H__
    #define __PV_H__
    
    //初始化semnums个信号灯/信号量的值(value)
    extern int I(int semnums, int value);
    
    //对信号集(semid)中的信号灯(semindex)作P(value)操作
    extern void P(int semid, int semindex,int value);
    
    //对信号量集(semid)中的信号灯(semindex)作V(value)操作
    extern void V(int semid, int semindex, int value);
    
    //销毁信号量集(semid)
    extern void D(int semid);
    
    #endif

    //pv.c

    #include "pv.h"
    #include <sys/sem.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <malloc.h>
    #include <assert.h>
    
    //编译命令:gcc -o bin/pv.o -Iinclude -c src/pv.c 
    
    /*封装对信号集的PV操作*/
    
    //定义信号量集的Union
    union semun{
        int                val;
        struct semid_ds    *buf;
        unsigned short     *array;
    };
    
    //创建信号量集,并初始化semnums个信号灯/信号量的值为value
    int I(int semnums, int value)
    {
        //创建信号量集
        int   semid = semget(IPC_PRIVATE, semnums, IPC_CREAT | IPC_EXCL | 0777);
        if(semid < 0){
            return -1; //错误时
        }
    
        union semun un;
        unsigned short* array = (unsigned short*)calloc(semnums, sizeof(unsigned short));
        
        int i = 0;
        for(; i< semnums; i++){
            array[i] = value;
        }
    
        un.array = array;
        //初始化信号量集中的所有的信号灯的初值
        //0:表示要初始化所有的信号灯
        if(semctl(semid, 0, SETALL, un) < 0){
            perror("semctl error");
            return -1;
        }
    
        free(array);
    
        return semid; //返回信号量集ID
    }
    
    //对信号集(semid)中的信号灯(semindex)作P(value)操作
    void P(int semid, int semindex, int value)
    {
        assert(value >= 0);
        
        /*
         * 定义sembuf类型的结构体数组,放置若个结构体变量
         * 对应要操作的信号量、要作的P或V操作
         */
        struct sembuf ops[] = {{semindex, -value, SEM_UNDO}};//只有1个元素,表示只操作
                                                             //一个信号量.-value表示P操作
        
        if(semop(semid, ops, sizeof(ops)/sizeof(ops[0])) < 0){
            perror("P semop error");
        }
    }
    
    //对信号量集(semid)中的信号灯(semindex)作V(value)操作
    void V(int semid, int semindex, int value)
    {
        assert( value >= 0);
        
        /*
         * 定义sembuf类型的结构体数组,放置若个结构体变量
         * 对应要操作的信号量、要作的P或V操作
         */
        struct sembuf ops[] = {{semindex, value, SEM_UNDO}};//只有1个元素,表示只操作
                                                             //一个信号量。+value表示V操作
        if(semop(semid, ops, sizeof(ops)/sizeof(ops[0])) < 0){
            perror("semop error");
        }
    }
    
    //销毁信号量集(semid)
    void D(int semid)
    {
        if(semctl(semid, 0, IPC_RMID, NULL) < 0){
            perror("semctl error");
        }
    }

    //account.h

    #ifndef __ACCOUNT_H__
    #define __ACCOUNT_H__
    
    typedef struct
    {
        int      code;    //帐号
        double   balance; //余额
        int      semid;   //在共享资源上绑定一个信号量集
    }Account;
    
    //取款
    extern double withdraw(Account* a, double amt); //amt == amount
    //存款
    extern double deposit(Account* a, double amt);
    //查看帐户余额
    extern double get_balance(Account* a);
    
    #endif  //__ACCOUNT_H__

    //account.c

    #include "account.h"
    #include "pv.h"
    #include <string.h>
    #include <assert.h>
    
    //取款
    double withdraw(Account* a, double amt) //amt == amount
    {
        assert(a != NULL);
        
        //对信号量集semid中的0号信号灯作P操作
        P(a->semid, 0, 1);   //P操作
        
        if(amt < 0 || amt > a->balance){
            V(a->semid, 0, 1);
            return 0.0;
        }
    
        double balance = a->balance; //先取余额
        sleep(1); //为模拟进程下可能出现的问题
    
        balance -= amt;
        a->balance = balance; //更新余额。在读取余额和更新余额之间有
                              //故意留出“时间窗口”。
        
        //对信号量集semid中的0号信号灯作V操作
        V(a->semid, 0, 1);  //V操作   
        
        return amt;    
    }
    
    //存款
    double deposit(Account* a, double amt)
    {
        assert(a != NULL);
        
        if(amt < 0){
            return 0.0;
        }
        
        P(a->semid, 0, 1); //P操作
    
        double balance = a->balance; //先取余额
    
        sleep(1); //为模拟多进程下可能出现的问题
    
        balance += amt;
        a->balance = balance; //更新余额。
        
        V(a->semid, 0, 1);  //V操作
    
        return amt;    
    }
    
    //查看帐户余额
    double get_balance(Account* a)
    {
        assert(a != NULL);
        
        P(a->semid, 0, 1); //P操作
        double balance = a->balance;
        V(a->semid, 0, 1); //V操作
    
        return balance;
    }

    //account_test.c

    #include "account.h"
    #include "pv.h"
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/shm.h>
    
    int main(void)
    {
        //在共享内存中创建银行帐户
        int shmid;
        if((shmid = shmget(IPC_PRIVATE, sizeof(Account), IPC_CREAT | IPC_EXCL | 0777)) < 0){
            perror("shmget error");
            exit(1);
        }
    
        //进程共享内存映射(a为返回的映射地址)
        Account* a= (Account*)shmat(shmid, 0, 0);
        if(a == (Account*)-1){
            perror("shmat error");
            exit(1);
        }
    
        //银行帐户初始化
        a->code = 100001;
        a->balance = 10000;
        printf("balance: %f
    ", a->balance);
    
        //创建信号量集并初始化
        a->semid = I(1, 1);//1个信号量,初始值为1
        if(a->semid < 0){
            perror("I(1, 1) init error");
            exit(1);
        }
        
        //父子进程都进行取款
        pid_t pid;
        if((pid = fork()) < 0){
            perror("fork error");
            exit(1);
        }else if(pid > 0){ //parent process
            //父进程进行取款操作
            double amt = withdraw(a, 10000);
            printf("pid %d withdraw %f from code %d
    ", getpid(), amt, a->code);
           
            int semid = a->semid;
    
            //解除映射
            shmdt(a);
    
            wait(0);
            
            //删除共享内存区
            shmctl(shmid, IPC_RMID, NULL);
            
            //销毁信号量
            D(semid);
            
        }else{ //child process
            //子进程会继承父进程映射的共享内存地址
            //子进程进行取款操作
            double amt = withdraw(a, 10000);
            printf("pid %d withdraw %f from code %d
    ", getpid(), amt, a->code);
    
            //解除映射
            shmdt(a);
        }
    
        return 0;
    }
    /*输出结果:
     [root@localhost 11.IPC]# gcc -o bin/account_test -Iinclude src/pv.c src/account.c src/account_test.c
     [root@localhost 11.IPC]# bin/account_test
     balance: 10000.000000
     pid 2229 withdraw 10000.000000 from code 100001
     pid 2230 withdraw 0.000000 from code 100001
     */

    【编程实验】进程信号量实现的读者/写者的同步

    (1)目的:利用进程信号量的PV操作实现进程间的同步问题

    (2)在共享内存中读写数据(读者和写者问题)

    (3)设置两个信号量并初始化为0:控制读的信号量I(s1, 0),控制写的信号量I(s2,1)

    //read_writer.c

    #include <sys/shm.h>
    #include <sys/sem.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    
    //共享资源
    typedef struct{
        int val;
        int semid; //一个资源绑定一定信号量集
    }Storage;
    
    //初始化信号量集
    void init(Storage* s)
    {
        assert(s != NULL);
        //创建信号量集(包含2个信号量)
        if((s->semid = semget(IPC_PRIVATE, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0){
            perror("semget error");
            exit(1);
        }
        
        //对信号 量中的所有信号量初始化
        union semun{
            int              val;
            struct semid_ds  *ds;
            unsigned short   *array;
        };
    
        union semun un;
        //信号量的初始设置
        unsigned short array[2] = {0, 1}; //分别为控制读、写
        un.array = array;
        if(semctl(s->semid, 0, SETALL, un) < 0){
            perror("semctl error");
            exit(1);
        }
    }
    
    //销毁信号量集
    void destroy(Storage* s)
    {
        assert( s != NULL);
    
        if(semctl(s->semid, 0, IPC_RMID, NULL) < 0){
            perror("semctl error");
            exit(1);
        }
    }
    
    void write(Storage* s, int value)
    {   
        struct sembuf ops_p = {1, -1, SEM_UNDO};
        struct sembuf ops_v = {0,  1, SEM_UNDO};
    
        //等待可写:P(s1)操作
        if(semop(s->semid, &ops_p, 1) < 0){
            perror("semop error");    
        }
    
        s->val = value; //写入数据
        printf("%d write %d
    ", getpid(), value);
    
        //通知己可读:V(s0)
        if(semop(s->semid, &ops_v, 1) < 0){
            perror("semop error");
        }
    }
    
    void read(Storage* s)
    {   
        struct sembuf ops_p = {0, -1, SEM_UNDO};
        struct sembuf ops_v = {1,  1, SEM_UNDO};
    
        //等待可读:P(s0)操作
        if(semop(s->semid, &ops_p, 1) < 0){
            perror("semop error");    
        }
    
        int value = s->val; //读取数据
        printf("%d read %d
    ", getpid(), value);
    
        //通知己可写:V(s1)
        if(semop(s->semid, &ops_v, 1) < 0){
            perror("semop error");
        }
    }
    
    int main(void)
    {
        //将共享资源Storage创建在共享内存中
        int shmid;
        if((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | 0777)) < 0){
            perror("shmget error");
            exit(1);
        }
    
        //共享内存映射
    
        Storage* s = (Storage*)shmat(shmid, 0, 0);
        if(s == (Storage*)-1){
            perror("shmat error");
            exit(1);
        }
    
        //创建信号量集并初始化
        init(s);
    
        pid_t pid = fork();
    
        if(pid < 0){
            perror("fork error");
            exit(1);
        }else if (pid > 0){ //parent process
            int i = 1;
            for(; i<=10; i++){
                write(s, i);
            }
    
            wait(0);
            destroy(s);//释放信号量集
            shmdt(s); //解除映射
            shmctl(shmid, IPC_RMID, NULL); //删除共享内存
        }else{ //child process
            int i = 1;
            for(; i<=10; i++){
                read(s);
            }
    
            shmdt(s);//解除共享内存映射
        }
    
        return 0;
    }
    
    /*输出结果
     [root@localhost 11.IPC]# bin/read_writer
     2376 write 1
     2377 read 1
     2376 write 2
     2377 read 2
     2376 write 3
     2377 read 3
     2376 write 4
     2377 read 4
     2376 write 5
     2377 read 5
     2376 write 6
     2377 read 6
     2376 write 7
     2377 read 7
     2376 write 8
     2377 read 8
     2376 write 9
     2377 read 9
     2376 write 10
     2377 read 10
     */
  • 相关阅读:
    HBase 文件读写过程描述
    Kafka 部署指南-好久没有更新博客了
    《Python高性能编程》——列表、元组、集合、字典特性及创建过程
    Ansible常用功能
    vim内替换文件内容
    线程队列-queue
    Python多进程
    python多线程知识-实用实例
    夜间模式的实现
    本地通知的实现
  • 原文地址:https://www.cnblogs.com/5iedu/p/6593802.html
Copyright © 2011-2022 走看看