操作系统结合哲学家进餐问题分析如何预防死锁

Posted 九死九歌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统结合哲学家进餐问题分析如何预防死锁相关的知识,希望对你有一定的参考价值。

零、哲学家进餐问题


  哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

  哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。

  首先我们先来看这样一串代码及其运行结果:

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static final Semaphore[] chopsticks = new Semaphore[5];

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						chopsticks[j].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}



  很明显,死锁住了。我们要必然要避免死锁,如何避免呢?

  那我们就要分析死锁是如何产生的。

  死锁产生的四个条件分别是:① 互斥条件、② 不剥夺条件、③ 请求和保持条件、④ 循环等待条件,我们只要破坏掉任何一个条件就可以避免死锁了。

一、破坏互斥条件

  很遗憾的是,这种策略并不能应用于哲学家进餐问题。并不是像内存一样同一时刻可以有多个进程访问,同一个筷子同一时刻只能让一个哲学家使用,也就是互斥访问,所以破坏互斥条件无法应用于哲学家进餐问题。

  spooling技术:各个需要使用打印机的进程把需要输出的内容交给输出进程,输出进程负责调用打印机,其他进程无需进入阻塞状态。把互斥资源改造成了共享资源。

二、破坏不剥夺条件


  java API中的Semaphore实现类的无参acquire()方法是无法获取资源就一直等待直到有资源,而他的有参函数tryAcquire(long timeout, TimeUnit unit)则是尝试获取一份资源,若timeout个时间单位内都没有获得,则放弃手中资源并返回false,TimeUnit是时间单位的枚举类型。

  解决方案:

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class MealOfPhilosopher {

	static final Semaphore[] chopsticks = new Semaphore[5];
	static final Semaphore mutex = new Semaphore(1);

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						if (!chopsticks[j].tryAcquire(10, TimeUnit.SECONDS))
							System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他左边的" + j + "号筷子");
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						if (!chopsticks[(j + 1) % 5].tryAcquire(10, TimeUnit.SECONDS))
							System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

三、破坏请求和保持条件


  而在哲学家进餐问题中我们可以通过一个互斥锁mutex或者java语言的synchronized块来进行静态分配

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static Semaphore[] chopsticks = new Semaphore[5];
	static Semaphore mutex = new Semaphore(1);

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						mutex.acquire();
						chopsticks[j].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						mutex.release();
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}
import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static final Semaphore[] chopsticks = new Semaphore[5];

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {

						synchronized(chopsticks) {
							chopsticks[j].acquire();
							System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
							chopsticks[(j + 1) % 5].acquire();
							System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						}

						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

四、破坏循环等待条件


  顺序资源分配法如果某进程一次申请一号资源、二号资源,然而他先试用二号资源,在使用一号资源,就会使得一号资源长期空闲。

  应用到代码里面他具体就是,给筷子分成0~4号,0号哲学家先拿左手0后拿右手1,其他都是先拿先左后右,只有四号左手是4,右手是0,所以必须先拿右再拿左。

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static final Semaphore[] chopsticks = new Semaphore[5];

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			if (j < 4) {
				new Thread(() -> {
					while (true) {
						try {
							chopsticks[j].acquire();
							System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
							chopsticks[j + 1].acquire();
							System.out.println(Thread.操作系统王道考研 p28-31 死锁的概念死锁的处理决策:预防避免检测和解除死锁

哲学家进餐问题的死锁问题

王道操作系统OS进程管理

Java并发编程死锁

Java总结—实现Runnable接口创建线程,线程安全同步,死锁(哲学家进餐问题),读写锁

操作系统 王道考研2019 第二章:进程管理 -- 吸烟者问题 & 读者-写者问题 & 哲学家进餐问题