浅谈协程

在程序遇到性能瓶颈的时候,解决方案之一就是采用并发编程技术。程序员

尤为是使用Python这种“执行低效”的编程语言,如何用其实现高效地并发能力被屡屡提起。因为众所周知的缘由,在别的语言中经常使用的多线程并发编程模型在Python里不那么好了。编程

有群不明就里的闲蛋(闲的蛋疼的人),认为GIL让多线程没法并行执行,他的生命的天空就是灰蒙蒙的。多进程太耗系统资源,多线程又不让好好玩,因而,用Python的同窗在稍有编程经验后,就会尝试去弄明白Python菜鸟老鸟都常挂在嘴边的协程是什么。安全

相关概念

通过与无数不明就里就想用代码干翻全世界的人打交道,发现他们每每不尊重、不重视基本常识。(本文对基本常识的解释也许可能存在有误解的地方,欢迎在公众号后台留言批评指正。)服务器

进程(Process)

在面向进程设计的操做系统中(如Linux 2.4及其以前),进程是程序运行的基本单位;而在面向线程设计的系统中(如Linux 2.6以后),进程只是线程的容器,操做系统调度任务的单位是线程,进程只是用于隔离不一样的进程,不一样进程间资源不共享,进程内能够资源共享。数据结构

操做系统使用进程模型,是为了实现多任务操做系统。例如,让用户能够边听音乐边玩网游。多进程多是真正的利用多核CPU并行执行,也可能只是分时复用。进程的基本模型和基本行为,都是由操做系统定义的,编程语言只能遵守实现。多线程

有人可能不解,为啥Erlang里的进程,就和操做系统定义的不同呢?由于不管是进程、线程,最终都是须要用代码来编写和实现的,接口的定义权虽然在操做系统那里,而编程语言却控制着具体实现。Erlang虽然有本身的“进程”模型,但只是Erlang单方面的设计把它叫作“Process”,和操做系统定义的进程是两码事。它其实是使用操做系统的线程接口实现的,故而实质是线程。并发

子进程、父进程、主进程

能够从一个进程中启动别的进程,新启动的称为子进程,启动子进程的进程称为父进程,原始最早执行的进程称为主进程。编程语言

线程(Thread)

线程是现代操做系统调度任务的基本单位,是进程的组成部分。同一进程下的多个不一样线程,能够共享该进程拥有的计算机资源,各个线程之间所拥有的资源通常不共享。函数

操做系统使用线程模型,是为了提供任务分解为多个子任务并发或并行运行的解决方案,以提升程序执行效率。例如,网游程序既要与服务器传输信息,也要采集用户的键盘鼠标操做,还要渲染游戏画面,均可以拆解为独立子任务分派到不一样线程去执行。多线程能够真正利用多核CPU并行执行,亦可分时复用。线程的基本模型和基本行为,也是由操做系统定义的,编程语言只能遵守实现。性能

有人可能又不解,为啥操做系统说线程能够并行执行,而Python里的线程却不能并行呢?刚提到具体的实现权力在编程语言。Python为了下降麻瓜们编写并发程序的难度,引入了GIL锁的概念,让一个进程内的多线程,只能利用单核,多线程只能分时复用单核轮流执行。这种方式,有好有坏,好处是下降并发编程难度,大大减小并发编程的反作用,坏处是不能利用多核优点。

子线程、父线程、主线程

与进程的父子关系相似。能够从一个线程中启动别的线程,被启动的为子线程,原来的是父线程,原始最早执行的为主线程。

例程(Routine)

语言级别内定义的可被调用的代码段,为了完成某个特定功能而封装在一块儿的一系列指令。通常的编程语言都用称为函数方法的代码结构来体现。

子例程(Subroutine)

例程中定义的例程。注意,因为例程能够嵌套定义,并且例程也本就是代码拆分设计的子程序,因此,子程序、子例程、例程等概念常常相互混用。在英文技术文章里,routine和subroutine这两个词几乎不做区分,在讨论嵌套和被嵌套这种对比之下才有区分。

并发(Concurrent)


并发描述的是程序的组织结构。指程序要被设计成多个可独立执行的子任务(以利用有限的计算机资源使多个任务能够被实时或近实时执行为目的)。

例如玩网游,须要将客户端服务端数据交换的任务和图形绘制的任务拆分为独立子任务,要让交换一下数据就当即绘制一下图造成为可能,在单核CPU上也能够有较好的游戏体验。若是不拆分,实现这种效果将会变得很是困难。

并行(Parallel)


并行描述的是程序的执行状态。指多个任务同时在多个CPU上执行(以利用富余计算机资源加速完成多个任务为目的)。则称这些任务是并行执行的。这样的程序称为能够并行执行的程序。

如上述玩网游例子,假如在四核机器上执行,和服务器交互的任务单独享用一个核,图形绘制任务再拆分为3个独立子任务分别单独享用一个核,这样不管是与服务器交互,仍是图形的计算和渲染都会更快更流畅。

并发提供了一种程序组织结构方式,让问题的解决方案能够并行执行,但并行执行不是必须的

协程(Co-routine)

见名知义,协做式的例程。下面深刻解析。

协程是非抢占式的多任务子例程的归纳,能够容许有多个入口点在例程中肯定的位置来控制程序的暂停与恢复执行。多个入口点是指能够在一个协程内屡次使用如yield的关键字,每一个yield的位置,都是程序员可使之让出执行权、暂停、恢复、传递信号、注入执行结果等操做。

高德纳说,例程是协程的特例。暂不深刻解析这句话,但咱们应该知道了,协程、例程本质上是一回事,不过表现有所差别。例程,就是函数、方法,因此协程在代码的体现,也就是按照函数、方法那样去定义的。

函数在线程内执行,协程固然也在线程内执行,多个协程共享着该线程拥有的资源。因为协程就是函数或方法,在线程运行初始化时,因此,与函数同样,协程的数据结构存放在线程的栈内存中。因此协程的切换,实际上仍是函数的调用,是在线程的栈内存中完成的。 进程和线程的切换,要保存的状态很复杂不少,内存占用量也要大不少,涉及的操做系统调度也复杂不少。这就是协程的切换开销比线程和进程都小太多的缘由。

注意,协程是能够跨线程调度的,就像一个函数能够放到另外一个线程去执行同样。

协程和进程或线程的不一样之处。协程要有多少个入口点(即yield多少次)、和接下来调用哪一个协程(即yield谁)、各自运行什么任务(占用多少资源)都是程序猿在编程中实现的。这既是优势也是缺点,可见,要用协程写出高质量的并发代码,对程序员的质量有很高的要求。

进程和线程都是由操做系统来调度的,何时中断、何时返回、接下来调度谁,都是操做系统包办。并且都是抢占式调度,优先级平等的多进程和多线程的执行顺序是不可预测的,而协程的执行顺序是能够被安排的

协程和通常例程(函数/方法)的区别。函数执行是从其第一行开始,一直到返回为止(没有显式return语句的也有返回)。从开始到返回,执行完了,就退出了,生命周期随之结束。函数在各次调用之间,并不会保存以前的执行状态。而协程,有多个入口点,可能会被调度屡次,一个协程的暂时退出,是靠调用别的协程实现的。协程的调用,还会保存以前的执行状态,切换到另外一个协程后,能够再回来继续往下执行。协程执行的起点,是进入该协程的入口点,不必定是协程定义的第一行代码,该次调用的终点,也不必定是协程的最后一行代码。

协程是用户态线程吗?

你特么在逗我。这种说法,是在无故增长人们理解协程的负担。协程,不是用户态线程,也不是用户(程序员)控制着的线程(若是换成“相似线程的东西”,勉强过得去)。

那再啰嗦一下什么是用户态,以及用户态线程。

操做系统在执行代码的时候,会对代码区别对待,使代码具备不一样的权限,意味着不一样的代码段能够操控不一样的内存区域和各类计算机资源的访问。就是为了实现耳熟能详的“保护模式”。保护模式是为了不程序员提供给系统执行的代码影响到系统的稳定性和安全性。

因此,操做系统内核的代码几乎都是有特权的,所谓的内核态,Ring0级特权;而程序员编写的应用程序,大可能是面向通常用户,几乎都是没有特权的,所谓用户态,Ring3普通权限。因此,一段代码是什么等级,就说这段代码就正处于该等级对应的状态。

然而,程序员也可能须要操做系统底层资源,好比要在系统内植入病毒、木马。当某病毒线程经过中断门、调用门等方式进入内核态破坏操做系统的时候,它当时是内核态线程;若是这个病毒它一下子又要为你下载日本电影作单纯地文件访问,那它当时就是用户态线程。

明白了吗?别再说协程是用户态线程,这样显得读的书少。

相关文章
相关标签/搜索