进程与线程基本概念

本章主要讲述进程与线程的起源、区别与作用。

大部分内容摘抄自其它文章。建议查看最后的引用部分,查看原文内容加深理解。


进程与线程基本概念

进程与线程

进程的提出

最初的计算机只能接受一些特定的指令,用户每输入一个指令,计算机就做出一个操作。当用户在思考或者输入时,计算机就在等待。这样效率非常低下,在很多时候,计算机都处在等待状态。

批处理操作系统

把一系列需要操作的指令写下来,形成一个清单,一次性交给计算机。

批处理操作系统在一定程度上提高了计算机的效率,但是由于批处理操作系统的指令运行方式仍然是串行的,内存中始终只有一个程序在运行,后面的程序需要等待前面的程序执行完成后才能开始执行,而前面的程序有时会由于I/O操作、网络等原因阻塞,所以批处理操作效率也不高

进程的提出

内存中能不能存在多个程序呢? 科学家们提出了进程的概念。

进程就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间互不干扰。进程保存着程序每一个时刻运行的状态

程序:用某种编程语言(java、pyhton等)编写,能够完成一定任务或者功能的代码集合,是指令和数据的有序集合,是一段静态代码

CPU采用时间片轮转的方式运行进程: CPU为每个进程分配一个时间段,称作它的时间片。如果在时间片结束时进程还在运行,则暂停这个进程的运行,并且将CPU分配还给另一个进程(这个过程叫做上下文切换)。如果进程在时间片结束前阻塞或者结束,则CPU立即进行切换,不用等待时间片用完。

当进程暂停时,它会保存当前进程的状态(进程表示、进程使用的资源等),在下一次切换回来时根据之前保存的状态恢复,接着继续执行

使用进程+CPU时间片轮转方式的操作系统,在宏观上看起来同一时间执行多个任务,换句话说,进程让操作系统的并发成为了可能。虽然并发从宏观上看有多个任务在执行,但在事实上,对于单核CPU来说,任意具体时刻都只有一个任务在占用CPU资源

对操作系统的要求进一步提高

虽然进程的出现,使得操作系统的性能大大提升,但是随着时间的推移,人们并不满足一个进程在一段时间内只能做一件事情,如果一个进程有多个子任务时,只能逐个执行这些子任务,很影响效率。

线程的提出

基于上述问题,人们提出线程的概念,让一个线程执行一个子任务,这样一个进程就包含了多个线程,每个线程负责一个单独的子任务。

总之,进程和线程的提出极大的提高了操作系统的性能。进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。

多进程的方式可以实现高并发,为什么我们要使用多线程?

多进程方式确实可以实现并发,但使用多线程有以下几个好处:

  • 进程间的通信比较复杂,而线程间的通信比较简单,通常情况下,我们需要使用共享资源,这些资源在线程间的通信比较容易。
  • 进程是重量级的,而线程是轻量级的,故多线程方式的系统开销更小。

进程和线程的区别

进程是一个独立的运行环境,而线程是进程中执行的一个任务。他们两个的本质区别是是否单独占用内存地址空间及其他系统资源(比如I/O)

  • 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
  • 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高。一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
  • 进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。

另外一个重要区别就是,进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位(即CPU分配时间的单位)

上下文切换

上下文切换概念

上下文切换是指CPU从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时刻CPU寄存器和程序计数器的内容。

上下文切换的性能问题

并发任务需要进行线程创建以及上下文的切换,所以效率不如串行块

如何减少上下文切换

  • 无锁并发编程,CAS算法,减少并发,使用最小线程,协程
  • 无锁并发编程:避免使用锁,比如数据分段执行(MapReduce),尽可能使用无状态对象,避免竞争情况等
  • CAS算法: java.util.current包中大量使用CAS算法,比如Atomic,AQS等等
  • 减少并发:Java8 中新引入的LongAdder,DoubleAdder等新类,将CAS算法替换成value分担原则
  • 使用最小线程:避免创建不必要的线程,当任务很少但线程很多时,会导致大量线程为等待状态
  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
  • 补充: 需要注意的是,Java的线程是映射到操作系统的原生线程上,因此需要阻塞或唤醒一个线程都需要操作系统的协助,这就意味着要从用户态转换到核心态,因此状态转化是非常耗费处理器时间的

参考资料

  1. RedSpider社区成员原创与维护的Java多线程系列文章
  2. kiraSally的个人博客
  3. CSDN博主「和尚要吃肉」的『JAVA内存模型JMM
  4. 《Java并发编程的艺术》