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开发的自我修养

MapReduce中map并行度优化及源码分析

  一个job的map阶段并行度由客户端在提交job时决定,而客户端对map阶段并行度的规划的基本逻辑为:将待处理数据执行逻辑切片(即按照一个特定切片大小,将待...

732
来自专栏挖坑填坑

Asp.net+Vue2构建简单记账WebApp之二(使用ABP迅速搭建.Net后台)

ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。 ASP.NET Boilerplate是一个用最佳实践...

863
来自专栏岑玉海

hbase源码系列(十一)Put、Delete在服务端是如何处理?

在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了《HTable探秘》的朋友都会有印象,没看过的建议...

3689
来自专栏知识分享

红外接收控制灯亮灭

先上图 功能 红外接收头接收到红外信号,控制一个灯翻转 ? 使用74HC123是因为红外接收头接收的是一连串的高低电平,,在接收到第一个电平的跳变沿的时候就控制...

2726
来自专栏技术碎碎念

读取含有BOM头的文件遇到的问题

需求是读取一个csv文件,然后解析成对应的数据结构。csv必须包含指定的某些列,通过列名header来进行校验。 解析配置文件的方法。 1 public Li...

3277
来自专栏码匠的流水账

java10系列(二)Local-Variable Type Inference

本文主要解读下java10的Local-Variable Type Inference

461
来自专栏大内老A

Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序

一、为什么CallHandler需要进行排序 PIAB为我们提供了一个很好地实现AOP的方式。AOP旨在实现Business Logic和Non-Busines...

17310
来自专栏大内老A

开发自己的Data Access Application Block[上篇]

经常在网上看到对ORM的讨论沸沸扬扬,我也来凑个热闹,谈谈我写的一个ORM。最近在做一项工作,把我们经常用到的一些业务逻辑抽象出来,写成一个个的Applicat...

1718
来自专栏程序猿DD

Spring Cloud Feign的文件上传实现

在Spring Cloud封装的Feign中并不直接支持传文件,但可以通过引入Feign的扩展包来实现,本文就来具体说说如何实现。 ? 服务提供方(接收文件) ...

30711
来自专栏码匠的流水账

kafka stream errorlog报警实例

log4j-core-2.7-sources.jar!/org/apache/logging/log4j/core/appender/mom/kafka/Kaf...

621

扫码关注云+社区