linux网络编程之System V 信号量(二):用信号量实现进程互斥示例和解决哲学家就餐问题

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

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

程序如下:

#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成功。

程序如下:

#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\n", no);
        sleep(DELAY);
        printf("%d is hungry\n", no);
        wait_for_2fork(no);
        printf("%d is eating\n", 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》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

Java基础-day07-知识点相关题-自定义数据类型;ArrayList

Java基础-day07-知识点相关题 一、定义类:包含main()方法,按以下要求编写程序: 1)实例化一个ArrayList,只存储String数据: 2)...

3628
来自专栏编码小白

activiti学习笔记(一) 获取流程配置实例

activiti获取流程配置实例 流程配置的获取方法api        流程配置的获取共有七种方法,但是只分为三类,一类是根据配置文件路径进行获取、一类是根据...

3629
来自专栏程序员宝库

Spring 注解概览

从Java5.0开始,Java开始支持注解。Spring做为Java生态中的领军框架,从2.5版本后也开始支持注解。相比起之前使用xml来配置Spring框架,...

3628
来自专栏蓝天

带有通配符的字符串匹配算法-C/C++

日前某君给我出了这样一道题目:两个字符串,一个是普通字符串,另一个含有*和?通配符,*代表零个到多个任意字符,?代表一个任意字符,通配符可能多次出现。写一个算法...

1283
来自专栏LanceToBigData

struts2(三)之表单参数自动封装与参数类型自动转换

前言   对struts2的使用不外乎这几点,参数自动封装,拦截器的使用,数据校验,ognl表达(值栈和actionContext的讲解),struts2的标签...

21210
来自专栏逆向与安全

从虚拟机角度看Java多态->(重写override)的实现原理

工具与环境: Windows 7 x64企业版 Cygwin x64 jdk1.8.0_162 openjdk-8u40-src-b25-10_feb_201...

890
来自专栏Young Dreamer

setInterval(code, time)中code传递参数办法

1.使用setInterval的场景 有时我们需要隔一定的时间执行一个方法,这时就会用到setInterval,但是由于这个方法是浏览器模拟出的Timer线程,...

1679
来自专栏有趣的Python

2-Java面向对象-封装

将类的某些信息隐藏在类内部, 不允许外部程序直接访问;通过该类提供的方法来实现对隐藏信息的操作和访问;

822
来自专栏玩转JavaEE

SpringBoot+WebSocket实现在线聊天(二)

本文是vhr系列第15篇,项目地址:https://github.com/lenve/vhr 在线聊天使用了SpringBoot+WebSocket实现,为...

4575
来自专栏企鹅号快讯

每周四更面试题:True+True=?

面试题:True + Ture == ? Python 的 “+” 号会根据操作对象数据类型的不同而进行重载,操作对象为数字类型时,它是算术运算符;操作对象为序...

1797

扫码关注云+社区