进程、线程和协程的区别与联系

进程

进程就是应用程序的启动实例。进程拥有代码和打开的文件资源、数据资源、独立的内存空间。

进程调度

也叫作业调度,算法包括:

  1. 先来先服务(FCFS,First-Come-First-Served): 按照作业到达后备作业队列(或进程进入就绪队列)的先后次序来选择作业(或进程)。
  2. 短作业优先(SJF,Shortest Process Next):这种调度算法主要用于作业调度,它从作业后备队列中挑选所需运行时间(估计值)最短的作业进入主存运行。
  3. 时间片轮转调度算法(RR,Round-Robin):当某个进程执行的时间片用完时,调度程序便停止该进程的执行,并将它送就绪队列的末尾,等待分配下一时间片再执行。然后把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程,在一给定的时间内,均能获得一时间片处理机执行时间。
  4. 高响应比优先(HRRN,Highest Response Ratio Next): 按照高响应比((已等待时间+要求运行时间)/ 要求运行时间)优先的原则,在每次选择作业投入运行时,先计算此时后备作业队列中每个作业的响应比RP,然后选择其值最大的作业投入运行。
  5. 优先权(Priority)调度算法:按照进程的优先权大小来调度,使高优先权进程得到优先处理的调度策略称为优先权调度算法。优先数越多,优先权越小。
  6. 多级队列调度算法:根据作业的性质和类型的不同,将就绪队列再分为若干个子队列,所有的进程按其性质排入相应的队列中,不同队列采用不同的调度算法。

参考:操作系统的进程调度算法

进程间的通信方式

  1. 管道( pipe ):半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (named pipe) : 半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 消息队列( message queue): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 信号 ( sinal ) : 用于通知接收进程某个事件已经发生。
  6. 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  7. 套接字socket:与其他通信机制不同的是,它可用于不同机器间的进程通信。

状态及转换

在这里插入图片描述

进程同步与互斥

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
即,同步体现一种协作性,互斥体现的是一种排他性。

线程

一般程序猿都主要关注线程部分即可。

线程与进程区别

  • 粒度性分析:线程的粒度小于进程。
  • 调度性分析:进程是资源拥有的基本单位,线程是独立调度与独立运行的基本单位,除寄存器,程序计数器等必要的资源外基本不拥有其他资源。
  • 系统开销分析:由于线程基本不拥有系统资源,所以在进行切换时,线程切换的开销远远小于进程。

线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。线程拥有自己的栈空间。
无论进程还是线程,都是由操作系统所管理的。对操作系统来说:
进程是资源管理和分配基本单元
线程是调度、执行的基本单元
线程是系统分配处理器时间资源的基本单位?

线程模型

用户线程位于内核之上,它的管理无需内核支持;而内核线程由操作系统来直接支持与管理。
用户线程和内核线程对应关系:

  • 多对一模型(用户级线程模型),线程的创建、调度、同步的所有细节全部由进程的用户空间线程库来处理。
    • 优点:用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换,使线程的创建、调度、同步等非常快。
    • 缺点:由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行;内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等。
    • 许多语言实现的协程库基本上都属于这种方式,比如python的gevent。
  • 一对一模型(内核级线程模型),内核负责每个线程的调度,可以调度到其他处理器上面。
    • 优点:实现简单。
    • 缺点:对用户线程的大部分操作都会映射到内核线程上,引起用户态和内核态的频繁切换;内核为每个线程都映射调度实体,线程过多,对性能有影响。
    • Java使用的是一对一线程模型。
  • 多对多模型(两级线程模型),前两者的结合,用户线程一般多于内核线程。
    • 区别于多对一模型,多对多模型中的一个进程可以与多个内核线程关联,于是进程内的多个用户线程可以绑定不同的内核线程;
    • 区别于一对一模型,它的进程里的所有用户线程并不与内核线程一一绑定,而是可以动态绑定内核线程, 当某个内核线程因为其绑定的用户线程的阻塞操作被内核调度让出CPU时,其关联的进程中其余用户线程可以重新与其他内核线程绑定运行。
    • 所以,多对多模型既不是多对一模型那种完全靠自己调度的也不是一对一模型完全靠操作系统调度的,而是中间态(自身调度与系统调度协同工作),因为这种模型的高度复杂性,操作系统内核开发者一般不会使用,所以更多时候是作为第三方库的形式出现。
    • 优点:兼具多对一模型的轻量;对应多个内核线程,则一个用户线程阻塞时,其他用户线程仍然可以执行;对应多个内核线程,则可以实现较完整的调度、优先级等;
    • 缺点:实现复杂
    • goroutine调度器就是采用的这种实现方案,在Go语言中一个进程可以启动成千上万个goroutine,自带高并发。

线程通信方式

  1. 锁机制:包括互斥锁、条件变量、读写锁
    互斥锁提供以排他方式防止数据结构被并发修改的方法。
    读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
    条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  3. 信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

状态

Java中线程具有五种状态:初始化、可运行、运行中、阻塞、销毁。转化关系如下:
在这里插入图片描述
线程不同状态之间的转化是谁来实现的呢?是JVM吗?
并不是。JVM需要通过操作系统内核中的TCB(Thread Control Block)模块来改变线程的状态,这一过程需要耗费一定的CPU资源。

线程之间是如何进行协作的呢?
如生产者/消费者模式,但性能不高:

  1. 涉及到同步锁。
  2. 涉及到线程阻塞状态和可运行状态之间的切换。
  3. 涉及到线程上下文的切换。

死锁

进程、线程、协程都会发生死锁。下面以进程为例,其他皆适用。

死锁产生的原因:

  • 竞争资源;
  • 进程推进顺序不当。

死锁产生的必要条件:

  1. 互斥条件:一个资源一次只能被一个进程所使用,即是排它性使用。
  2. 不剥夺条件:一个资源仅能被占有它的进程所释放,而不能被别的进程强占。
  3. 请求与保持条件:进程已经保持至少一个资源,但又提出新的资源要求,而该资源又已被其它进程占有,此时请求进程阻塞,但又对已经获得的其它资源保持不放。
  4. 环路等待条件:当每类资源只有一个时,在发生死锁时,必然存在一个进程-资源的环形链。

预防死锁:破坏四个必要条件之一

死锁的避免:银行家算法,该方法允许进程动态地申请资源,系统在进行资源分配之前,先计算资源分配的安全性。若此次分配不会导致系统从安全状态向不安全状态转换,便可将资源分配给进程;否则不分配资源,进程必须阻塞等待。从而避免发生死锁。

死锁定理:S为死锁状态的充分条件是:尚且仅当S状态的资源分配图是不可完全简化的,该充分条件称为死锁定理。

死锁解除:

  • 方法1:强制性地从系统中撤消一个或多个死锁的进程以断开循环等待链,并收回分配给终止进程的全部资源供剩下的进程使用。
  • 方法2:使用一个有效的挂起和解除机构来挂起一些死锁的进程,其实质是从被挂起的进程那里抢占资源以解除死锁。

线程阻塞

线程阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。Java 提供大量方法来支持阻塞:

  1. sleep()方法:sleep() 允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU时间,指定时间一过,线程重新进入可执行状态。常被用在等待某个资源就绪的情形,测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止。
  2. suspend() 和 resume() 方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生结果后,调用 resume() 使其恢复。
  3. wait() 和 notify() 方法:两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态。
  4. yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行足够的时间从而转到另一个线程。

协程

简介

协程,Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程

异步编程工具:协程、Futrue、Lamda。
分布式系统中编程,涉及非常多的网络通信,不可避免需要借助于回调编程,不利于代码阅读。为了解决回调函数这种对于代码可读性的破坏作用,提出很多改进方法,包括协程。协程延续同步编程习惯,但不同于多线程的是,协程并不会同时运行,它只是在需要阻塞的地方,用Yield()切换出去执行其他协程,然后当阻塞结束后,用Resume()回到刚刚切换的位置继续往下执行。这相当于可以把回调函数的内容,接到Yield()调用的后面。
在这里插入图片描述
缺点:
Resume()的代码还是需要在所谓“主线程”中运行。用户必须自己从阻塞恢复的时候,去调用Resume()。
需要做栈保存,在切换到其他协程之后,栈上的临时变量,也都需要额外占用空间,限制协程代码的写法,让开发者不能用太大的临时变量。

协程:可以用类似同步的方法来写异步程序,而无需把代码塞到不同的回调函数里面。yield关键字所在的代码行,类似return,但是又代表着后续某个时刻,程序会从yield的地方继续往下执行。这样就把那些需要回调的代码,从函数中得以解放出来,放到yield的后面。在很多客户端游戏引擎中,写的代码都是由一个框架,以每秒30帧的速度在反复执行,为了让一些任务,可以分别放在各帧中运行,而不是一直阻塞导致“卡帧”,使用协程就是最自然和方便的——Unity3D就自带协程的支持。

协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。
这样带来的好处就是性能得到很大的提升,不会像线程切换那样消耗资源。

语言支持

  • Lua:Lua从5.0版本开始使用协程,通过扩展库coroutine来实现。
  • Python:通过 yield/send 的方式实现协程。在python 3.5以后, async/await 成为更好的替代方案。实例:
def consume():
    while True:
        # consumer协程等待接收数据
        number = yield
        print('消费', num)


consumer = consume()
# 让初始化状态的consumer 协程先执行起来,在yield处停止
next(consumer)
for num in range(0, 100):
    print('生产', num)
    # 发送数据给consumer协程
    consumer.send(num)

创建一个叫做consumer的协程,并且在主线程中生产数据,协程中消费数据。
yield是python语法。当协程执行到yield关键字时,会暂停在那一行,等到主线程调用send方法发送数据,协程才会接到数据继续执行。
但是,yield让协程暂停,和线程的阻塞是有本质区别的。协程的暂停完全由程序控制,线程的阻塞状态是由操作系统内核来进行切换。
因此,协程的开销远远小于线程的开销。

  • Go:Go对协程的实现非常强大而简洁,可以轻松创建成百上千个协程并发执行。
  • Java:Java的原生语法中并没有实现协程,某些开源框架模拟出协程的功能,如:https://github.com/kilim/kilim

待学习

并发之痛 Thread,Goroutine,Actor

参考

死磕 java线程系列之线程模型
编码复杂度和通信

原创文章 131 获赞 175 访问量 32万+

猜你喜欢

转载自blog.csdn.net/lonelymanontheway/article/details/104459668