引自我在知乎上的回答: 进程 线程 协程 例程 过程 的区别是什么? - 骏马金龙的回答 - 知乎
首先解释下程序, 进程, 上下文切换和线程. 然后再解释协程, 例程, 过程.
程序: 源代码堆起来的东西. 相当于一个一动不动没有生命的机器人.
虽然是没有生命的机器人, 但是它被设计后就表示有了硬件, 它的硬件决定了之后它有生命后是如何干活的
机器人有优劣, 所以有些优秀的机器人干活很快, 而有些机器人干活很慢
进程: 程序在系统上跑起来 (运行) 之后的东西(动态的). 相当于有了生命的机器人. 生命是内核给的, 动起来的能力是 CPU 提供的驱动力.
因为在操作系统看来, 它已经有了生命, 会赋予它一些属性, 比如它叫什么(PID), 谁发明的(PPID), 它在哪个范围内活动(地址空间).................
内核会记录这个机器人的信息
机器人可以造机器人(父子进程)
它可以中断或睡眠. 比如机器人想充电, 但是没电给它, 它得等
内核可以跟它交流, 比如传递一些数据给它, 传递信号给它
它可以被毁掉. 比如进程崩溃或正常退出或被信号终止
机器人毁掉后要为它收尸, 如果机器人偷偷死掉, 就会没人给它收尸而变成僵尸进程
严格地说, 这个机器人运行起来之后虽然有了生命, 但是没有灵魂, 只有 CPU 给它驱动力的那一段时间, 他才能动起来, 其它时候都是在哪里睡觉
.......
上下文切换: 在某一时刻, 一核 CPU 只能执行一个进程. 相当于内核只能让一核 CPU 为一个机器人提供驱动力让它动起来(1:1), 多核 CPU 可以同时为多个机器人提供驱动力.
但是操作系统上有很多机器人在干活, 所以内核要控制 CPU 不断的为不同机器人来回提供驱动力, 这是进程切换(这是站在内核的角度上看的, 也叫上下文切换)
为了让你感觉机器人没有停止工作, 内核控制只给每个机器人一点点的 CPU 时间片. 这就相当于转起来的电扇, 你感觉没有间隙, 其实是有的, 只是间隙时间太短, 人眼难辨. 现在可以脑部一下, 一大堆的机器人在你面前完全不停的跳着鬼步舞....
因为 CPU 要切换给不同的机器人提供驱动力, 所以每次切换之前的机器人干活到了哪里以及它的状态得记录下来, 这是上下文保留(保护现场)
保护现场是必要的额外的工作, 频繁上下文切换会浪费 CPU 在这些额外工作上
.........
线程: 一个进程内可以有多个执行线程, 或者说线程是进程内部的小型进程. 相当于在机器人内部根据机器人自身克隆了很多个基本完全相同的体内小机器人.
进程内部可以有多个线程同时执行
进程内的所有线程拥有的源代码完全相同. 只是有些小型机器人执行任务 A, 有些小型机器人执行任务 B. 而这些任务原本是应该被那个大机器人完成的
所有小型机器人活动范围相同, 即某进程内所有线程都共享地址空间. 但是每个小型机器人也得有自己的一点私人空间(线程有自己的栈空间)
小型机器人共享很多属性(都来源于大机器人), 也有很多自己的属性
线程也要切换. 只是线程切换需要做的额外工作要比进程切换少的多
现在, 对比一下多进程和多线程?
然后是协程, 例程, 过程. 但是在解释这个之前, 先解释下现在更为俗知的函数, 以及它们和进程, 线程之间的关系.
函数: 一种代码段. 用来表示一个要完成的任务单元. 当然, 这个任务里可能也包含了其它多个子任务.
再说程序:
程序的主体部分是函数, 包括一个程序的入口函数 (main 函数, 有些语言(一半是动态语言) 不要求你敲 main 函数的声明, 这些语言会在程序执行的时候默默给你加上 main)以及其它一些自己编写的函数
除了函数外, 程序中可能还有一些全局属性的定义, 一些额外的属于编程语言自身的额外代码, 比如说程序文件头部可能声明这是一个包以及要导入什么包之类的
函数是要被进程 (或线程) 执行的, 机器人干什么的? 就是执行函数的. 程序的运行从执行入口函数 main 开始, 然后在 main 中调用其它你自己编写的函数, 也就是说跳转到其它函数上去执行一个个的任务. 画重点, 函数是要被执行的, 函数的执行是可以进行跳转的.
那些非函数代码(比如全局属性的定义代码, 导包代码), 是在程序被装载准备执行的时候完成好的工作.
先总结一下重点: 进程, 线程是机器人, 函数是机器人要干的活.
机器人是怎么执行函数的呢? 在不使用 coroutine(也就是国人翻译后的 "协程")的情况下, 它的执行流程是固定的: 从 main 函数开始, 跳转执行其它函数 A,main 函数被搁置等待, 它必须等待跳转后的函数 A 执行完返回后才能回到 main 函数继续向下执行, 如果函数 A 中还有调用函数 B, 那么函数 A 被搁置等待, 它必须等待函数 B 执行完返回后才继续向下执行. 直到 main 函数也执行完, 程序退出.
继续用机器人来比喻, 那就是机器人要执行一个任务, 但这个任务中要求临时去执行另一个任务, 那么这个机器人必须先去执行另一个任务, 并只能在执行完另一个任务的时候回到之前的那个任务继续执行.
所以, 重点是: 函数 A 中在第 X 行开始跳转调用函数 B 时, 函数 A 必须等待函数 B 执行完返回后才能继续从第 X 行处开始继续向下.
回到正题要解释的东西: 协程, 例程, 过程.
很遗憾, 例程, 过程, 子程序, 它们都是函数的不同称呼, 不同的时代, 不同的编程语言称呼的方式不一样, 都是任务单元. 甚至面向对象里的方法也是函数, 只不过在面向对象的面具之下, 它有一些和面向对象相关的特性.
最后是协程. 我猜你说的协程应该是 coroutine 这类东西, 全称是 cooperative routine, 也叫做 cooperative tasks. 只看英文的话, 意思已经很明显了: 协同运行的子程序(子程序就是例程, 函数, 过程). 或者说是协同执行的任务.
在解释 coroutine 之前, 多插一句嘴.
coroutine 被翻译为协程, 个人认为是不太合理的. 在 shell 中有 coproc 的概念(ksh/bash 等一些 shell 都支持 coproc 的功能),cooperative process, 表示协同运行的进程, 按单词字面意思, 这才应该被翻译为协程.
协同运行? 这是个什么意思. 回顾下刚才解释函数 (因为是 co routine, 所以我下面全部用 routine 来替换函数的说法) 的执行流程在理解协同运行是什么意思.
在正常情况下, routine 跳转运行后必须原地等待跳转后的那个 routine 执行完返回才能继续从原地向下执行.
但是, 使用 coroutine 的时候, 假设 routine1 和 coroutine2 互为 coroutine, 那么 routine1 跳转到 routine2 去执行的时候, 它会等待 routine2 才能继续向下执行, 但是不一定是等待 routine2 执行完, 也可能是等待 routine2 重新跳转回 routine1(因为 routine2 已经产生了一些可以让 routine1 继续运行的数据, 而不是让 routine1 继续在那里阻塞). 同理 routine2 也可能会原地等待 routine1,routine1 再跳回 routine2.
但是这怎么行, 来来回回的跳转总得退出吧? 这就是跟所写的代码有关系了. 比如某个 routine 中加入一个判断, 达到某一条件时就不再跳转, 而是直接向下执行.
机器人的比喻又来了. 机器人要执行一个任务, 但要求临时去执行另一个任务, 现在不强制规定必须执行完另一个任务才回来执行原始任务, 而是可以在执行另一个任务的时候, 又临时回来执行原始任务.
如果说不好理解, 那么下面这个 wiki 中给的生产者消费者模型的伪代码很容易帮助理解 coroutine, 这个程序不一定合理, 但非常适合于理解 "协同" 的意义. 其中 q 是一个队列, 生产者 coroutine 会跳转到消费者 coroutine, 同理消费者 coroutine 也一样会跳转到生产者 coroutine.
- var q := new queue
- coroutine produce
- loop
- while q is not full
- create some new items
- add the items to q
- yield to consume
- coroutine consume
- loop
- while q is not empty
- remove some items from q
- use the items
- yield to produce
这就是协同运行以及 coroutine 的解释, 我想应该已经不难理解了.
最后, 说一下 coroutine 的优点:
实际上, coroutine 可以认为是单线程多任务的工作方式(当然, 进程中实现 coroutine 也是可以的), 因为它在单个线程中的多个任务之间直接跳转, 而多线程是通过上下文切换来实现多任务的. 换句话说, coroutine 提供了并发却不并行的功能. 通过 coroutine, 还能实现更为 "实时" 的上下文任务, 因为 coroutine 之间的跳转切换不需要任何系统调用和可能的阻塞调用, 不需要像多线程一样为了线程之间的资源同步而使用额外的互斥锁, 信号量等.
来源: https://www.cnblogs.com/f-ck-need-u/p/10802716.html