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

本文主要是介绍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》

转载自http://blog.csdn.net/jnu_simba/article/details/9101581

这篇关于linux网络编程之System V 信号量(二):用信号量实现进程互斥示例和解决哲学家就餐问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/300485

相关文章

PostgreSQL中rank()窗口函数实用指南与示例

《PostgreSQL中rank()窗口函数实用指南与示例》在数据分析和数据库管理中,经常需要对数据进行排名操作,PostgreSQL提供了强大的窗口函数rank(),可以方便地对结果集中的行进行排名... 目录一、rank()函数简介二、基础示例:部门内员工薪资排名示例数据排名查询三、高级应用示例1. 每

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u