尝试用通俗的方式解释协程
协程(英文:Coroutine)这个概念其实并不复杂,但我却花了很多时间理解,后来仔细一想,大概是因为这个概念穿插了很多别的概念,所以这篇文章将这些概念全部疏通一遍再来理解协程。
维基百科是这么定义的:
协程是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。
字我都认识,可连起来是什么意思?这就是本文写作的目的。
进程和线程
协程和线程对比起来更容易理解,因为他俩实在太像了。
线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
那么什么是进程呢?
进程
进程是程序运行的一个实例(字面拆解为正在进行的程序),一个程序是静态的二进制文件,是没有灵魂的,当我们启动程序时就会开启一个进程,这个时候系统开始读取程序的二进制文件,并且向系统申请一些资源。
总结起来进程就是一个程序运行的时候系统环境变量和用到的资源以及本身代码的集合,其特点是每个 CPU 核心任何时间内仅能运行一项进程,即同一时刻执行的进程数不会超过核心数,这对支持更高并发是个阻碍,并且为了解决进程阻塞的问题,操作系统遍引入了更轻量的线程。
进程资源分配的最小单位,线程是操作系统能够进行运算调度的最小单位。可以这么理解:在进程这个大圈子中,存在着各种资源,在没有线程时,这些资源全部由一个可执行代码调配,这有点像单线程进程。当引入线程之后,一个进程下可以有很多线程,相当于一个可执行代码被分成了很多段,这些片段可以单独执行,并且使用所在进程内的资源。
当然,要使一个应用程序完整运行起来就必须要把这些细分的线程全都执行起来,于是便需要时间片轮转。操作系统为每一个线程分配 CPU 执行时间(通常为几百毫秒),当运行这个线程的时间超过分配的执行时间时,系统会强制 CPU 去执行下一个等待的线程(补充一下,线程和进程都是有状态的,这里这个正在”等待“的线程应该是”中断“状态),如此快速的不断切换线程便实现了并发。同时程序运行的时候也只会出现线程阻塞,而不是整个进程阻塞,如此便解决了上面的问题。
并发
不知不觉提到了并发,根据上面的描述不难看出,并发是指一段时间内(程序开始运行到结束的这段时间)执行多个程序(线程算是一个进程的子程序)。
协程
上面提到,线程是为了解决阻塞和并发的并发问题(在一段时间内执行更多的程序),类似的,协程也是为了在一段时间运行更多的“程序”(应该说是函数)并且可以避免线程阻塞。有了之前的铺垫,类比起来讲协程就很容易了。
线程和协程解决的并发问题不是一个问题,线程是为了让操作系统并发运行程序,以达到”同时“运行更多程序的目的,而协程是为了让一个线程内的程序并发服务更多内容,这里我不太好解释,一个直观的例子就是一个单线程的服务器程序同时服务多个用户,如何做到服务更多用户?想想线程是怎么来的,我们只需要把这个线程中的程序继续细分,然后像时间片轮转一样不断的去执行这些细分的“子程序”。即使一个这样的“子程序”执行发生阻塞,也不会导致整个线程阻塞,在这个“子程序”阻塞的时候切换到其他“子程序”继续服务,既解决了阻塞的问题,也实现了并发。大概理解了吧,协程就是线程中可以交替运行的代码片段。
下面说说真实的协程,线程切换是由操作系统的时间片控制的,而协程是程序自己实现的,让协程不断轮流执行才是实现并发,所以实现协程还必须要有一个类似于时间片的结构,不同于线程的切换,协程的切换不是按照时间来算的,而是按照代码既定分配,就是说代码运行到这一行才启动协程,协程是可以由我们程序员自己操控的。
所以尝试控制这些协程是很有趣的事,一起期待下一篇《JavaScript中的协程》吧!