• 用信号量实现进程互斥示例和解决哲学家就餐问题


    一、我们在前面讲进程间通信的时候提到过进程互斥的概念,下面写个程序来模拟一下,程序流程如下图:


    即父进程打印字符O,子进程打印字符X,每次打印一个字符后要sleep 一下,这里要演示的效果是,在打印程序的边界有PV操作,故每个进程中间sleep 的时间即使时间片轮转到另一进程,由于资源不可用也不会穿插输出其他字符,也就是说O或者X字符都会是成对出现的,如OOXXOOOOXXXXXXOO....

    程序如下:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
     
    #include<sys/types.h>
    #include<unistd.h>
    #include<errno.h>
    #include<sys/ipc.h>
    #include<sys/sem.h>
    #include<sys/wait.h>

    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)

    union semun
    {
        int val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
    };

    int semid;
    /* pv操作之间的临界区,导致打印的字符一定是成对出现的 */
    void print(char op_char)
    {
        int pause_time;
        srand(getpid());
        int i;
        for (i = 0; i < 10; i++)
        {
            sem_p(semid);
            printf("%c", op_char);
            fflush(stdout);
            pause_time = rand() % 3;
            sleep(pause_time);
            printf("%c", op_char);
            fflush(stdout);
            sem_v(semid);
            pause_time = rand() % 2;
            sleep(pause_time);
        }

    }

    int main(void)
    {

        semid = sem_create(IPC_PRIVATE);
        sem_setval(semid, 1);
        pid_t pid;
        pid = fork();
        if (pid == -1)
            ERR_EXIT("fork");

        if (pid > 0)
        {

            print('o');
            wait(NULL);
            sem_d(semid);

        }

        else
        {

            print('x');

        }


        return 0;
    }

    sem_create 等函数参考工具集。在调用semget 时指定key = IPC_PRIVATE,表示创建的是私有的信号量集,但具有亲缘关系的进程是可见的,比如父子进程。输出如下:

    simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./print 
    ooxxooxxooxxooxxooooooxxooxxooxxooxxxxxx

    可以看到输出都是成对出现的字符。

    分析一下:semval = 1,假设父进程先被调度执行,父进程先P了一下,此时 semval = 0,子进程在父进程睡眠时间被调度的时候尝试P,semval = -1,然后子进程阻塞了,父进程打印完V了一下,semval = 0,唤醒子进程,子进程的P操作返回,打印字符睡眠后V了一下,semval = 1。当然在子进程睡眠的时候父进程可能也在尝试P,故就一直循环往复下去。


    二、哲学家就餐问题的描述可以参考这里,下面我们尝试解决这个问题的方法是:仅当一个哲学家两边筷子都可用时才允许他拿筷子。


    上图中红色数字表示哲学家的编号,总共5个哲学家,用5个进程来表示;黑色数字表示筷子的编号,总共有5根筷子,可以定义一个信号量集中含有5个信号量,每个信号量的初始值为1,当某个哲学家可以同时得到两根筷子(同时P两个信号量返回)时可以用餐,否则阻塞等待中。用餐后需要同时V一下两个信号量,让其他进程可以P成功。

    程序如下:

     C++ Code 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
     
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/ipc.h>
    #include<sys/msg.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<errno.h>
    #include<sys/ipc.h>
    #include<sys/sem.h>
    #include<sys/wait.h>

    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while(0)

    union semun
    {
        int val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux-specific) */
    };

    int semid;

    #define DELAY (rand() % 5 + 1)

    void wait_for_2fork(int no)
    {
        int left = no;
        int right = (no + 1) % 5;

        struct sembuf buf[2] =
        {
            {left, -1, 0},
            {right, -1, 0}
        };

        semop(semid, buf, 2);
    }

    void free_2fork(int no)
    {
        int left = no;
        int right = (no + 1) % 5;

        struct sembuf buf[2] =
        {
            {left, 1, 0},
            {right, 1, 0}
        };

        semop(semid, buf, 2);
    }

    void philosopere(int no)
    {
        srand(getpid());
        for (; ;)
        {

            printf("%d is thinking ", no);
            sleep(DELAY);
            printf("%d is hungry ", no);
            wait_for_2fork(no);
            printf("%d is eating ", no);
            sleep(DELAY);
            free_2fork(no);
        }
    }


    int main(void)
    {

        semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666);
        if (semid == -1)
            ERR_EXIT("semget");
        union semun su;
        su.val = 1;
        int i;
        for (i = 0; i < 5; i++)
        {
            semctl(semid, i, SETVAL, su);
        }

        int no = 0;
        pid_t pid;
        for (i = 1; i < 5; i++)
        {
            pid = fork();
            if (pid == -1)
                ERR_EXIT("fork");

            if (pid == 0)
            {
                no = i;
                break;
            }
        }

        philosopere(no);

        return 0;
    }

    我们在前面说过,当需要对一个信号量集中的多个信号量操作时,要么全部执行,要么全部不执行,即是一个原子操作,某个进程需要等待两根筷子,即对两个信号量同时P成功才可以用餐,信号量的序号是0~4,可看作筷子的编号,此时semop 函数操作的是2个信号量,即需定义2个struct sembuf 结构体成员的数组 struct sembuf buf[2]; 

    simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./dinning 
    0 is thinking
    3 is thinking
    2 is thinking
    4 is thinking
    1 is thinking
    4 is hungry
    4 is eating
    0 is hungry
    3 is hungry
    1 is hungry
    1 is eating
    2 is hungry
    3 is eating
    4 is thinking
    1 is thinking
    0 is eating
    4 is hungry
    0 is thinking
    1 is hungry
    1 is eating
    3 is thinking
    4 is eating
    0 is hungry
    1 is thinking
    2 is eating
    0 is eating
    4 is thinking
    2 is thinking
    1 is hungry
    3 is hungry
    3 is eating

    0 is thinking
    2 is hungry
    1 is eating
    4 is hungry

    ................

    如果发现程序没有运行卡着,即没有发生死锁现象,从中也可以发现同时最多只能有两个哲学家一起用餐,也不会出现相邻哲学家一起用餐的情况。


    参考:

    《UNP》

  • 相关阅读:
    实时视频应用示例:监控与音乐教学
    im ui框架调研,对比
    WampServer 常见问题
    Android Studio xcode单步调试 WebRTC Android & iOS
    WebRTC 源码分析(五):安卓 P2P 连接过程和 DataChannel 使用
    归并排序的分析与Java实现
    Redis的数据类型
    使用Maven进行多模块拆分
    内容平台消息队列异步静态化实现
    ActiveMQ的应用实例
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8473159.html
Copyright © 2020-2023  润新知