linux进程、线程、线程池和协程的由来

大家都知道进程教科书式的定义:进程是操作系统中运行的程序,是操作系统资源管理的最小单位。一个进程可以管理多个线程,线程相对轻量,且线程共享进程地址空间。

但是操作系统一开始是没有进程这个概念的,你有没有想过为何要有进程呢?出了什么样的问题导致进程的诞生?进程带来什么样的的好处?但同时进程出现后,还有什么问题需要线程来解决了?线程又有什么样的缺点?紧接着线程池的诞生解决该问题的同时,又暴露什么样的问题?协程的诞生解决该问题的同时还有什么问题?

从进程到线程,再到线程池,再到协程,什么场景下使用线程和线程池以及协程呢?你知道吗?

1.  批处理系统时代(无进程时代)

一开始计算机都是大型机,程序代码是机器码,直接穿孔把程序输入到纸带上面,根据纸带里的二进制数据进行逻辑运算,一个纸带输入完了,接着读取下一个纸带,只有等上一个处理运算结束之后才能排队到下一个。为了改进这种排队等候的低效率问题,就有人发明了批处理系统。批处理时代可以多个纸带一起提交,计算机会集中处理,或者多写几种可能,集中让计算机处理,总有一个结果是好的。

为了提升效率,机器码就被汇编语言替代了,从而再也不用一串串二进制数字来写代码了。但是问题也来了当程序在运算的时候,会一直占用着CPU资源,有可能某个时间在写磁盘数据、读取网络设备数据,一直霸占着CPU会造成资源的浪费,其实CPU空闲着也是浪费,这时候完全可以把CPU的计算资源让给其他程序,直到数据读写准备就绪后再切换回来。怎么解决这个问题呢?通过什么方式来管理这些程序呢?怎么控制和管理多个程序间的计算机资源呢?

2. 进程时代

CPU做逻辑运算的每条指令是从内存中读取的,但内存的大小是有限的,很可能装不下磁盘中的整个程序,内存中只允许运行当下需要的部分程序,等运算完继续读取后面一部分程序到内存并继续进行运算。怎么控制和管理多个程序间的计算机资源呢?划分资源管理的最小单元啊:进程,也就是上面说的内存中加载指令的最小单位。有了进程,操作系统才好在内存中加载程序的执行指令,方便调度程序在有限资源情况下更有效的利用资源,提高资源利用率。道理是相通的,在有限时间有限资源的情况下识别风险控制风险,进行刚刚好的,保证更高质量的输出。

3. 线程时代

即使划分了资源管理的最小单元,但是一个进程在运行的过程中,也不可能一直占据着CPU进行逻辑运算,运行过程中很可能在进行磁盘I/O或者网络I/O,资源还是有些浪费。为了更加充分利用CPU运算资源,提高资源利用率,有人设计了线程的概念。线程最大的特点是和创建它的进程共享地址空间。

为何非要设计出线程呢?将进程再划分小一点(争取在进程空闲时划分为小的进程),开多个进程也可以提升CPU的利用率,但是开多个进程的话,进程间通信又是个麻烦的事情,毕竟进程之间地址空间是独立的,没法像线程那样做到数据的共享,需要通过其他的手段来解决,比如管道等。

进程和线程的区别

一般操作系统都会分为内核态和用户态,用户态线程之间的地址空间是隔离的,而在内核态,所有线程都共享同一内核地址空间。不管是用户线程还是内核线程,都和进程一样,均由操作系统的调度器来统一调度(至少在Linux中是这样子)。

4. 线程池时代

线程本身的数据结构需要占用内存,频繁创建和销毁线程会加大系统的压力,如果开辟太多线程,系统调度的开销会很大。线程池就是在这样的场景下提出的,常见的线程池实现方案如图,线程池可以在初始化的时候批量创建线程,然后用户后续通过队列等方式提交业务逻辑,线程池中的线程进行逻辑的消费工作,可以在操作的过程中降低线程创建和销毁的开销,但是调度的开销还是存在的。

常见的线程池方案

5. 协程时代

在多核场景下,如果是I/O密集型场景,就算开多个线程来处理,也未必能提升CPU的利用率,反而会增加线程切换的开销。另外,多线程之间假如存在临界区或者共享数据,那么同步的开销也是不可忽视的。协程恰恰就是用来解决该问题的。协程是轻量级线程,在一个用户线程上可以跑多个协程,这样可以提升单核的利用率。

ps:涉及网络IO、磁盘IO的任务都是IO密集型任务,特点:CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。IO密集型任务执行期间,99%的时间花在IO上,花在CPU上的时间很少,因此用运行速度极快的C语言 ,完全无法提升运行效率。对于IO密集型任务,最合适的语言是开发效率最高(代码量最少)的语言。

在实际场景下,假如CPU有N个核,就只要开N+1个线程,然后在这些线程上面跑协程就行了。

协程不像进程或线程那样可以让系统负责相关的调度工作,协程处于一个线程当中,系统是无感知的,如果需要在该线程中阻塞某个协程的话,需要手工进行调度,如图所示。

协程的实现方案

要在用户线程上实现协程是一件很难受的事情,原理类似于调度器根据条件的改变不停地调用各个协程的callback机制,但前提是大家都在一个用户线程下。要注意,一旦有一个协程阻塞,其他协程也都不能运行了

6. 进程、线程、线程池、协程

个人认为,从无进程到提出进程是操作系统资源管理第一个重大质的飞跃;从进程到线程和线程池是第二大飞跃;从线程和线程池到协程是第三大质的飞跃。

多进程的出现是为了提升CPU的利用率,特别是I/O密集型运算,不管是多核还是单核,开多个进程必然能有效提升CPU的利用率。但进程间依然有资源利用优化空间,以及进程间通信的麻烦问题。

多线程则可以共享同一进程地址空间上的资源,能在资源的空闲时刻更好的利用,且不存在进程间通信的麻烦。但线程的创建和销毁会造成资源的浪费。

为了降低线程创建和销毁的开销,又出现了线程池的概念,在一开始就创建批量的线程。虽然减少了部分创建和销毁线程所消耗的资源,但调度的开销依然存在。

为了提升用户线程的最大利用效率,又提出了协程的概念,可以充分提高单核的CPU利用率,降低调度的开销(协程因不受操作系统资源管理的自动调度,如果需要可以手工或写代码调度)。

参考资料:陈科的《Linux内核分析及应用》1.1 线程和进程的概念

很认同作者的几句话,摘录在此处:

不管做什么工作,都需要搞明白所面临工作的过去、现在和未来。不懂历史的程序员肯定写不出好代码。不知道这个技术被创造出来到底意味着什么,也无法理解未来这个技术要向哪里发展,仅仅是解决当下的问题,修修补补,做一天和尚撞一天钟,仅此而已。

你可能感兴趣的:(linux进程、线程、线程池和协程的由来)