哲学家进餐问题pv_【jMiniLang】哲学家就餐问题

这篇博客通过jMiniLang演示了解决哲学家进餐问题的两种方法:使用PV操作和管程。首先,通过信号量实现互斥,创建并销毁信号量,模拟哲学家拿起和放下叉子的过程。接着,利用管程解决并发问题,通过wait和signal操作保证哲学家的就餐顺序,降低锁的竞争。最后,展示了管程在解决5个哲学家问题时形成1->3->5->2->4的稳定循环,证明了管程的有效性和wait/signal机制的优势。
摘要由CSDN通过智能技术生成

演示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个,那么就餐顺序又如何呢?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值