进程与线程
进程是一个程序对某个数据集的执行过程,是系统分配资源的基本单位。每个进程在内核中都有一个进程控制块(PCB)来维护进程的基本信息和运行状态。
线程是系统调度的基本单位,一个进程中可以有多个线程,它们共享进程资源。
进程与线程的区别:
- 资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
- 调度:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程的切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
- 开销:创建或撤销进程时的系统开销(分配或回收资源),远大于创建或撤销线程时的开销。同样地,进程切换时,需要保存当前进程的CPU环境,以及设置新调度进程的CPU环境,但是线程切换只需要保存和设置少量寄存器内容,开销很小。
- 通信:线程间的通信可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助IPC(管道、信号、消息队列、共享内存等)
进程状态与转换
进程调度算法
不同系统的调度算法目标不同,因此需要针对不同环境算法来讨论调度算法。
(1)批处理系统(是指用户将一批作业提交给操作系统后就不再干预,由操作系统控制它们自动运行)
批处理操作系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(作业从提价到终止的时间)。
- 先来先服务(First Come First Serve,FCFS):非抢占式的调度算法,按照请求的顺序进行调度,有利于长作业,不利用短作业,因为短作业必须一致等待前面的长作业执行完毕才能执行,造成短作业等待时间过长。
- 短作业优先:非抢占式的调度算法,按估计运行时间最短的顺序进行调度,长作业有可能会饿死,因为如果一直有短作业到来,那么长作业永远得不到调度。
- 最短剩余时间优先:最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。当一个新的作业到达时,其整个运行时间与当前进程的剩余时间比较,若新进程所需的时间更少,那么将当前进程挂起,运行新的进程。否则新的进程等待。
(2)交互式系统
交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。
- 时间片轮转:将所有就绪进程按照FCFS的原则排成一个队列,每次调度时,把cpu时间分给队首进程,该进程可以执行一个时间片,当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把cpu时间分配给队首的进程。(时间片轮转算法的效率和时间片的大小有很大关系。)
- 优先级调度:为每个进程分配一个优先级,按优先级进行调度。为了防止低优先级的进程永远等不到调度,可以随着时间的腿也增加等待进程的优先级。
- 多级反馈队列:设置多个队列,每个队列时间片大小都不同,进程在第一个队列没执行完,就会被移到下个队列(时间片大于上个队列);每个队列优先级也不同,最上面的优先权高,因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。(这种算法可以看作是时间片轮转调度算法和优先级调度算法的结合)
(3)实时系统
实时系统要求一个请求在一个确定时间内得到响应。分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。
进程同步
同步:多个进程因合作产生直接的制约关系,使得进程有一定的先后执行关系。
互斥:多个进程在同一时刻只有一个进程能进入临界区。
信号量:是一个整型变量,它有两个原子操作:P操作(对信号量执行-1操作)和V操作(对信号量执行+1操作),通常在执行这些操作的时候屏蔽中断。如果信号量的取值只能为0或者1,那么就成为了互斥量,0表示临界区已经加锁,1表示临界区解锁。
信号量实现生产者-消费者问题(java)
package sychronize;
/*Semaphore通过使用计数器来控制对共享资源的访问。
* 计数器大于0,则允许访问。
* 计数器为0,则拒绝访问
* 要访问资源,必须从信号零中授予线程许可
*/
import java.util.concurrent.Semaphore;
public class Test {
public static int count = 0;
public static final Semaphore mutex = new Semaphore(1);
public static final Semaphore empty = new Semaphore(10);
public static final Semaphore full = new Semaphore(0);
public static void main(String[] args) {
Producer p1 = new Producer();
Thread t1 = new Thread(p1,"生产者1");
Producer p2 = new Producer();
Thread t2 = new Thread(p2,"生产者2");
Producer p3 = new Producer();
Thread t3 = new Thread(p3,"生产者3");
Consumer c2 = new Consumer();
Thread t4 = new Thread(c2,"消费者1");
Consumer c3 = new Consumer();
Thread t5 = new Thread(c3,"消费者2");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
package sychronize;
//生产者
public class Producer implements Runnable{
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Test.empty.acquire(); //acquire():从信号量获得一个许可,如果无可用许可前将一直阻塞等待;
Test.mutex.acquire();
Test.count ++;
System.out.println(Thread.currentThread().getName()+"正在生产第:" + Test.count+"个商品!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
Test.mutex.release(); //release():释放一个许可
Test.full.release();
}
}
}
}
package sychronize;
//消费者
public class Consumer implements Runnable{
@Override
public void run() {
while(true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Test.full.acquire();
Test.mutex.acquire();
System.out.println(Thread.currentThread().getName()+"正在消费第:" + Test.count +"个商品!");
Test.count --;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
Test.mutex.release();
Test.empty.release();
}
}
}
}
信号量实现哲学家问题(java)
哲学家进餐问题解决:可以对哲学家进程施加一些限制条件
(1)最多允许四个哲学家同时进餐,这样可以保证至少一个哲学家是可以拿到筷子的;
(2)要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反,这样保证两个相邻的奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一支筷子,另一个会直接阻塞;
(3)仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。
package sychronize;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Semaphore;
public class Philosopher implements Runnable{
private int current; //当前哲学家的编号
private Philosopher left; //左边的哲学家
private Philosopher right; //右边的哲学家
private int[] state; //状态数组,每个哲学家三个状态:思考、饥饿、吃饭
private Semaphore mutex; //为了保证对state的安全读写
private int count;
private Semaphore[] semaphores; //信号量数组,每个哲学家要有一个信号量,方便其他人通知自己
public Philosopher(int current,int[] state, Semaphore[] semaphores,Semaphore mutex) {
this.current = current;
this.state = state;
this.semaphores = semaphores;
this.mutex = mutex;
this.count = 0;
}
public Philosopher getLeft() {
return left;
}
public void setLeft(Philosopher left) {
this.left = left;
}
public Philosopher getRight() {
return right;
}
public void setRight(Philosopher right) {
this.right = right;
}
public void think() throws InterruptedException {
mutex.acquire();
state[current] = 0;
mutex.release();
}
public void testTakeForks(Philosopher p) {
String time = new SimpleDateFormat("yyyy-MM-hh HH:mm:sss").format(new Date());
if(state[p.current] == 1 && state[p.left.current] != 2 && state[p.right.current] != 2) {
state[p.current] = 2; //表示拿到了一双筷子
semaphores[p.current].release();
System.out.println(time + "\t我是哲学家p"+current+"第\t"+(++count) +"次拿到筷子!");
}else {
System.out.println(time + "\t我是哲学家p"+current+"不具备拿到筷子的条件!");
}
}
public void takeForks() throws InterruptedException {
mutex.acquire();
state[current] = 1;
testTakeForks(this);
mutex.release();
semaphores[current].acquire(); //没有获得叉子的人,会被阻塞
}
public void eating() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void putForks() throws InterruptedException {
mutex.acquire();
state[current] = 0;
testTakeForks(left);
testTakeForks(right);
mutex.release();
}
public void philosopherDay() throws InterruptedException{
while(true) {
think();
takeForks();
eating();
putForks();
}
}
@Override
public void run() {
try {
philosopherDay();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package sychronize;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Arrays;
import java.util.concurrent.Semaphore;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/*
* 两个叉子要么都拿起来,要么一个不拿
*/
public class PhilosopherQues {
public static void main(String[] args) throws InterruptedException {
int count = 5;
int[] state = new int[5];
Semaphore[] semaphores = new Semaphore[5];
Semaphore mutex = new Semaphore(1);
for(int i=0;i<5;i++)
state[i] = 0;
for(int i=0;i<5;i++)
semaphores[i] = new Semaphore(0);
Philosopher p0 = new Philosopher(0,state,semaphores,mutex);
Philosopher p1 = new Philosopher(1,state,semaphores,mutex);
Philosopher p2 = new Philosopher(2,state,semaphores,mutex);
Philosopher p3 = new Philosopher(3,state,semaphores,mutex);
Philosopher p4 = new Philosopher(4,state,semaphores,mutex);
p0.setLeft(p4);
p0.setRight(p1);
p1.setLeft(p0);
p1.setRight(p2);
p2.setLeft(p1);
p2.setRight(p3);
p3.setLeft(p2);
p3.setRight(p4);
p4.setLeft(p3);
p4.setRight(p0);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(p0);
pool.submit(p1);
pool.submit(p2);
pool.submit(p3);
pool.submit(p4);
Thread.sleep(300);
}
}
管程
使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。
管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用。
管程引入了条件变量以及相关的操作:wait()和signal()来实现同步操作。对条件变量执行wait()操作会导致调用进程阻塞,把管程让出来给另一个进程持有;signal()操作用于唤醒被阻塞的进程。
进程通信
进程通信是指在进程之间传输数据。为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。
-
管道通信:管道是用于连接读写进程的一个共享文件,其实就是在内存中开辟一个大小固定的缓冲区。各进程要互斥地访问管道,当管道写满时,写进程将被堵塞,等待读进程将数据取走;当管道变空时,读进程将被阻塞。
管道分为:无名管道(一般使用fork函数实现父子进程通信);命名管道(没有血缘关系地进程也可以通信) -
消息队列:在内核中创建一个队列,队列中的每个元素是一个数据报,不同进程可以通过句柄访问这个队列,消息队列独立于发送和接受进程,可以通过顺序和消息类型读取。
-
信号量:信号量是一个计数器,用于多进程对共享数据的访问,这种通信方式主要用于解决进程同步相关问题。
-
共享存储:多个进程共享一个给定的存储区,不同进程可以及时看到对方进程中对共享内存中数据的更新,但是这种方式需要使用信号量来同步对共享存储的访问。
-
套接字:这种方法主要用于在客户端和服务器之间进行网络通信。
参考:
https://blog.csdn.net/weixin_41955327/article/details/118469590
http://www.cyc2018.xyz/
如有侵权,请联系作者删除