演示GIF(1.57MB),控制台输出效果(还有parser/vm/ui)自己实现:

信号量与PV原语

信号量为正数代表有空闲资源,为零与负数代表没有空闲资源。负数的绝对值代表等待的进程个数。信号量用(S,Q)表示,S是非负初值的整型变量,Q为进程等待队列。

P原语(阻塞):令S=S-1,若S>=0,进程继续执行,否则该进程变为等待状态,入队列Q

V原语(唤醒):令S=S+1,若S>0,进程继续执行,否则释放队列Q中第一个等待信号量的进程

实现互斥:令S=1。

用管道语义实现PV操作

当前jMiniOS支持的管道操作:create_pipe,创建管道,返回句柄

write_pipe,向管道中写入一个字符(非堵塞)

read_pipe,从管道中不断读取字符流(堵塞)

read_pipe_once,从管道中读取一个字符(堵塞)

destroy_pipe,销毁管道

用管道实现信号量:

var g_create_semaphore = func ~(name, n) {

if (call g_query_pipe(name)) {

return;

}

var pipe = call g_create_pipe(name);

call g_write_pipe(pipe, call g_string_rep("*", n));

return pipe;

};

export "g_create_semaphore";

var g_use_semaphore = func ~(name) {

return call g_create_pipe(name);

};

export "g_use_semaphore";

var g_destroy_semaphore = func ~(handle) {

call g_destroy_pipe_once(handle);

};

export "g_destroy_semaphore";

var g_lock_semaphore = func ~(handle) {

call g_read_pipe_once(handle);

};

export "g_lock_semaphore";

var g_unlock_semaphore = func ~(handle) {

call g_write_pipe(handle, "*");

};

export "g_unlock_semaphore";

关键:创建管道后,信号量的初始值=管道中存储的字符个数,即管道中的内容=信号量中可用资源。

P操作 = 向管道中写一字符

V操作 = 从管道中读一字符

Mutex实现,create_mutex = create_semaphore(1)

实现哲学家就餐问题

/* 哲学家就餐问题 */

var stage_philo = func ~() {

call word_typewrite("演示哲学家就餐问题! \n", 100);

var print_mutex = call g_create_mutex("philo_print");

foreach (var i : call g_range(1, 5)) {

call g_create_semaphore("fork_" + i, 1); // 每个叉最多能使用一次

}

var handles = [];

var philo = func ~(args) { // philo = 新建进程, args = 参数

var eat = call g_map_get(args, "eating");

var o = call g_map_get(args, "out");

var id = call g_map_get(args, "id");

var left_id = call g_map_get(args, "left_id"); // 左叉ID

var right_id = call g_map_get(args, "right_id"); // 右叉ID

var left = call g_use_semaphore("fork_" + left_id); // 左叉信号量

var right = call g_use_semaphore("fork_" + right_id); // 右叉信号量

call eat(o, "Philosophy#" + id + " ready");

foreach (var j : call g_range(1, 10)) { // 吃十次才饱

call g_lock_semaphore(left); // P左叉

call eat(o, "Philosophy#" + id + " is using fork#" + left_id);

call g_lock_semaphore(right); // P右叉

call eat(o, "Philosophy#" + id + " is using fork#" + right_id);

call eat(o, "Philosophy#" + id + " is eating. Process: " + j + "0%");

call g_unlock_semaphore(left); // V左叉

call g_unlock_semaphore(right); // V右叉

}

call eat(o, "Philosophy#" + id + " OK");

};

var eating = func ~(out, str) { // 输出

var pm = call g_use_mutex("philo_print");

call g_lock_mutex(pm);

foreach (var c : call g_range_string(str)) {

call g_write_pipe(out, c);

}

call g_write_pipe(out, '\n');

call g_task_sleep_ms(100);

call g_unlock_mutex(pm);

};

foreach (var j : call g_range(1, 5)) {

var args = {}; // 传参

call g_map_put(args, "eating", eating);

call g_map_put(args, "out", out);

call g_map_put(args, "id", j);

call g_map_put(args, "left_id", (j == 1) ? 5 : (j - 1));

call g_map_put(args, "right_id", (j == 5) ? 1 : (j + 1));

var h = call g_create_user_process_args(philo, args); // 新建进程

call g_array_add(handles, h);

call g_task_sleep(1);

}

call g_join_process_array(handles);

foreach (var k : call g_range(1, 5)) {

call g_destroy_semaphore(call g_use_semaphore("fork_" + k)); // 销毁

}

call g_destroy_mutex(call g_use_mutex("philo_print")); // 销毁

};

总结

从实例上看,哲学家不是同时开始就餐的,而是陆续就餐,同时就餐肯定会导致死锁问题。所以要有一定的防死锁和饥饿的解法,网上也有很多,有一种管程的解法。

例子中每个哲学家不会知道其他哲学家的状态,因此肯定会有缺陷啦,假如先让A就餐,然后A吃完再唤醒其他人,那就好办了。

唤醒语义,好东西,想想linux中的进程状态实现,soga。因此,我们的程序除了要支持lock/unlock之外,还要支持wait/notify,下回分解。

===========================================

0903 更新:用管程实现(jMiniLang语言)

/* 哲学家就餐问题 - 管程解决 */

var stage_philo2 = func ~() {

call word_typewrite("【管程】演示哲学家就餐问题! \n", 100);

call g_create_mutex("philo_print");

call g_create_mutex("philo_monitor");

var states = [];

call g_array_add(states, g_null);

foreach (var i : call g_range(1, 5)) {

call g_create_semaphore("philo_" + i, 1); // 记录每个哲学家的状态(管程)

call g_array_add(states, "thinking"); // 开始时哲学家都在思考

}

var handles = [];

var philo2 = func ~(args) {

var eat = call g_map_get(args, "eating"); // 拿参数

var states = call g_map_get(args, "states");

var o = call g_map_get(args, "out");

var id = call g_map_get(args, "id");

var left_id = call g_map_get(args, "left_id");

var right_id = call g_map_get(args, "right_id");

var monitor = call g_use_mutex("philo_monitor");

call eat(o, "Philosophy#" + id + " ready");

var enter = func ~() { // 进入临界区

var monitor = call g_use_mutex("philo_monitor");

call g_lock_mutex(monitor); // mutex自带等待队列

call g_printdn("Philosophy#" + id + " entered critical section");

};

var leave = func ~() { // 离开临界区

call g_printdn("Philosophy#" + id + " leaved critical section");

var monitor = call g_use_mutex("philo_monitor");

call g_unlock_mutex(monitor);

};

var wait = func ~(_id) { // 等待信号

var sem = call g_use_semaphore("philo_" + _id);

call g_printdn("Philosophy#" + _id + " waiting");

call g_lock_mutex(sem); // semaphore自带等待队列

};

var signal = func ~(_id) { // 发出信号

var sem = call g_use_semaphore("philo_" + _id);

call g_unlock_mutex(sem);

call g_printdn("Philosophy#" + _id + " received signal");

};

var test = func ~(_id) { // 测试哲学家是否具备进餐条件

var _left_id = (_id == 1) ? 5 : (_id - 1);

var _right_id = (_id == 5) ? 1 : (_id + 1);

if ((call g_array_get(states, _left_id) != "eating") && // 如果左右都不在进餐

(call g_array_get(states, _right_id) != "eating") &&

(call g_array_get(states, _id) == "hungry")) { // 且自己为饥饿状态

call signal(_id); // 发出就餐信号

} else {

call g_printdn("Test failed. #" + _left_id + ": " + call g_array_get(states, _left_id) +

", #" + _right_id + ": " + call g_array_get(states, _right_id) + ", #" +

_id + ": " + call g_array_get(states, _id));

}

};

var pickup = func ~() { // 拿起叉子

call enter();

call g_array_set(states, id, "hungry"); // 设置状态是饥饿

call test(id); // 看看自己能否用餐

call leave();

if (call g_array_get(states, id) != "eating") { // 如果尝试失败

call wait(id); // 等待

call g_array_set(states, id, "eating"); // 设置为进餐状态

} // 这里设置状态不会冲突,因为pickup只能由一个哲学家调用

};

var putdown = func ~() { // 放下叉子

call enter();

call g_array_set(states, id, "thinking"); // 设置状态是思考

call test(left_id); // 测试左边的哲学家可否就餐

call test(right_id); // 测试右边的哲学家可否就餐

call leave();

};

foreach (var j : call g_range(1, 10)) {

call eat(o, "Philosophy#" + id + " is thinking");

call pickup();

call eat(o, "Philosophy#" + id + " is eating. Process: " + j + "0%");

call putdown();

}

call eat(o, "Philosophy#" + id + " OK");

};

var eating = func ~(out, str) {

var pm = call g_use_mutex("philo_print");

call g_lock_mutex(pm);

foreach (var c : call g_range_string(str)) {

call g_write_pipe(out, c);

}

call g_write_pipe(out, '\n');

call g_task_sleep_ms(100);

call g_unlock_mutex(pm);

};

foreach (var j : call g_range(1, 5)) {

var args = {};

call g_map_put(args, "eating", eating);

call g_map_put(args, "states", states);

call g_map_put(args, "out", out);

call g_map_put(args, "id", j);

call g_map_put(args, "left_id", (j == 1) ? 5 : (j - 1));

call g_map_put(args, "right_id", (j == 5) ? 1 : (j + 1));

var h = call g_create_user_process_args(philo2, args); // fork

call g_array_add(handles, h);

}

call g_join_process_array(handles);

foreach (var k : call g_range(1, 5)) {

call g_destroy_semaphore(call g_use_semaphore("fork_" + k));

}

call g_destroy_mutex(call g_use_mutex("philo_print"));

};

从输出来看,哲学家就餐的顺序是1->3->5->2->4,是非常有规律的、重复的、完美的顺序,这说明了管程的使用是非常有效的,同时证明了wait/signal机制的优越性:降低了锁的竞争程度(引入休眠),同时唤醒机制保证了进程同步中各进程的运行顺序。也就是说,如果一开始就出现了1->3->5->2->4循环,那么这个循环就一直存在直到程序结束。

【深入】如果哲学家是6个、7个,那么就餐顺序又如何呢?

哲学家进餐问题pv_【jMiniLang】哲学家就餐问题相关推荐

  1. java 第六次实验_操作系统第六次实验报告——使用信号量解决哲学家进餐问题...

    0 个人信息 张樱姿 201821121038 计算1812 1 实验目的 通过编程进一步了解信号量. 2 实验内容 在服务器上用Vim编写一个程序:使用信号量解决任一个经典PV问题,测试给出结果,并 ...

  2. 经典的同步/互斥问题—哲学家进餐

    经典的同步/互斥问题-哲学家进餐 一.问题描述 设有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐.平时,一个哲学家进行思考,饥饿时 ...

  3. java 哲学家_Java哲学家进餐问题|多线程

    Java实验三 多线程 哲学家进餐问题 5个哲学家共用一张圆桌,分别坐在周围的5张椅子上, 在圆桌上有5个碗和5只筷子(注意是5只筷子,不是5双), 碗和筷子交替排列.他们的生活方式是交替地进行思考( ...

  4. 操作系统:哲学家进餐问题

    哲学家进餐问题 五个哲学家围着一张圆桌,每个哲学家面前放着食物.哲学家的生活有两种交替活动:吃饭以及思考.当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子. 下面是一种错 ...

  5. 【操作系统实验】Linux环境下用进程实现哲学家进餐问题——C语言完整代码+详细实验报告

    [注意]代码在文末,以下为详细实验报告 [实验目的]   以哲学家进餐问题为例,学习并熟悉Linux下进程通信.同步机制的具体实现方法,主要是了解并掌握信号量机制和避免死锁的使用方法,使得不会出现哲学 ...

  6. 操作系统 | OS 经典同步问题之生产者-消费者问题,哲学家进餐问题

    小目录 1. 生产者-消费者问题 2. 哲学家进餐问题 3. 吸烟者问题 1. 生产者-消费者问题 生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语 ...

  7. Linux哲学家进餐杀死进程,100分跪求“哲学家就餐问题”在 Linux下运行的源代码(后缀名为.c)!!!...

    如题. | 代码大致如下,当然不能直接使用,我没写P,V操作的函数. # define N 5 /* 哲学家数目 */ # define LEFT (i-1+N)%N /* i的左邻号码 */ # d ...

  8. 【操作系统】“哲学家进餐”问题

    "哲学家进餐"问题 有五个哲学家,他们的生活方式是交替地进行思考和进餐.他们共用一张圆桌,分别坐在五张椅子上. 在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用 ...

  9. PV操作经典例题——哲学家进餐问题

    哲学家进餐问题: 五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五只筷子,他们的生活方式是交替地进行思考和进餐.平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只 ...

最新文章

  1. AI、区块链和机器人:技术会让未来的工作发生什变化?
  2. python手机版iphone-python如何绘制iPhone手机图案?(代码示例)
  3. C++中比较大小的表达式中,小于号和大于号都是不能连着打的,要用连接起来
  4. 文档生成工具Sandcastle Help File Builder
  5. mysql 分组数据_MySQL基础之分组数据
  6. Kettle使用_0 Windows下安装图解
  7. (计算机组成原理)第二章数据的表示和运算-第二节7:详解C语言中的强制类型转换
  8. 百度EasyDL深度学习实战营,免费教你转型AI工程师!
  9. 一个两年Java的面试总结
  10. 重庆大学李婷婷计算机学院,北京大学青年研究中心赴我校考察调研
  11. Google 发布最新 IDC 能源消耗报告
  12. python绘制简单城市剪影图_Python之绘制个人足迹地图
  13. JAVA读取属性文件的几种方法
  14. 草根站长建站需要掌握或者了解的5种技术
  15. MKVToolNix v72.0 MKV视频封装工具
  16. stay here forever中文歌词
  17. 时间展示 星期几时英文需要显示成中文问题解决
  18. Chrome浏览器上集成IE内核方案
  19. LED亮5秒灭5秒C语言程序代码,单片机【木仓示申吧】_百度贴吧
  20. mysql数据库select语句用法_mysql数据库select查询语句简单用法

热门文章

  1. Lenovo ideapad 15isk y700 固态限速问题的解决办法
  2. 字符编码问题三个不可见的字符(0xEF-0xBB-0xBF,即BOM)
  3. 解决方案:Zotero实现参考文献中英文混排,将英文文献中的“等”转成“et al.”
  4. 《数据结构课程实践》_02_隐式图的搜索问题_实现
  5. 计算机专业知识串讲,计算机基础知识串讲
  6. 计算机电路基础综合题,计算机电路基础作业考试习题.doc
  7. Android系统Camera图片反转的一个问题
  8. android saf小结
  9. (FMD)辉芒MCU开发指南
  10. collections, time, queue的应用