【操作系统之一】进程、线程、协程
一、进程、线程、协程
1、概念
(1)进程
进程是系统资源分配的最小单位;启动一个应用程序就是开启了一个进程;进程拥有程序代码和打开的文件资源、数据资源、独立的内存空间。
进程的局限性是创建、撤销和切换的开销比较大。
(2)线程
线程是程序执行的最小单元,线程从属于进程,是程序的实际执行者;一个进程至少包含一个主线程,也可以有更多的子线程;线程拥有自己的栈空间。
优点:减小了程序并发执行的开销,提高了系统的并发性能。
缺点:线程没有自己的系统资源,只有运行时不可缺少的资源,但是同一进程的各线程可以共享进程所拥有的系统资源。对于某些独占资源存在锁机制,处理不当会出现死锁。
(3)协程
协程是一种用户态的轻量级线程;协程调度完全由用户控制,没有线程切换的系统资源开销;java关于协程第三方框架:Kilim
优点:协程执行效率高。因为子程序切换不是线程切换,由程序自身控制,没有线程切换的开销。协程不需要多线程的锁机制。在协程中控制共享资源不加锁,只需要判断状态就好
2、区别与联系
(1)进程与线程区别
1)资源方面:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程不可见
2)通信:进程间通信IPC,进程间可以直接读写进程数据段进行通信——需要进程同步和互斥手段辅助,以保证数据的一致性
3)调度和切换:线程上下文切换比进程上下文切换要快的多
4)在多线程OS中,线程不是一个可执行的实体
(2)协程与线程
1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU
2) 线程进程都是同步机制,而协程则是异步
3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
参考:
二、线程调度
1、并行并发
并行:同时做不同事情的能力,不同的代码块同时执行;
并发:交替做不同事情的能力,不同的代码块交替执行,多线程的并发运行,各个线程轮流获得CPU的使用权,分别执行各自的任务。
2、常见线程调度策略
1)时间片轮转(RR)
给每个作业赋予一个运行时间片,当时间片到则取下一个元素。是对FCFS和SJF算法的折衷,不会出现超时。
2)先来先服务(FCFS)
按照请求次序进行调度。实现简单不用对队列进行调整,但是可能因某个元素处理时间过长,出现等待超时。
3)基于优先权的调度算法(FPPS)
给作业赋予一定优先权,每次从最高优先权的元素中取出一个并执行。
4)多级队列调度(Multilevel feedback queue)
对任务进行分类,不同分类放置到不同队列中,可能会采用不同的调度算法,队列中元素视情况会在不同队列之间迁移
5)最短作业优先(SJF)
按照作业执行的消耗时间,消耗最短时间的先执行。需要预先估计作业执行时间,耗时较长的作业可能一直得不到执行。
选择调度策略:
实际实现中,经常是几种方案综合实现。例如windows将多级队列,优先权队列,时间片轮转,以及先进进出方式进行综合,每种优先权任务位于不同队列,高优先权采用时间片轮转方式调度,低优先权采用先进进出方式调度。有时根据任务等待或者执行程度会调整优先权队列中元素的优先权。
3、线程切换步骤
1)线程主要状态
①初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
②运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。
③RUNNING:就绪状态的线程在获得cpu时间片后变为运行中状态。
④阻塞(BLOCKED):表线程阻塞于锁。
⑤等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
⑥超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
⑦终止(TERMINATED):表示该线程已经执行完毕。
2)线程状态切换
2.1) 初始状态
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态
2.2) 就绪状态:就绪状态只是说有资格运行,调度程序没有挑选到你,你就永远是就绪状态。
调用线程的start()方法,此线程进入就绪状态;
当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态;
当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态;
锁池里的线程拿到对象锁后,进入就绪状态;
2.3) 运行中状态
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
2.4) 阻塞状态
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
2.5) 终止状态
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
2.6) 等待队列(本是Object里的方法,但影响了线程)
调用obj的wait(), notify()方法前,必须获得obj锁,也就是wait、notify方法必须写在synchronized(obj) 代码段内。
2.7) 同步队列状态
当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。
当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。
同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。
3、线程的核心方法
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIME_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
t.join()/t.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入TIME_WAITING状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。意思:我等会儿再用这把锁,CPU也让给你们,我先休息一会儿
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。意思:我用完了,你们谁用?
参考: