协程概念

协程的诞生

线程切换

在IO密集型的程序中由于IO操作远远小于CPU的操作,所以往往需要CPU去等IO操作。同步IO下系统需要切换线程,让操作系统可以再IO过程中执行其他的东西。这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带来了大量的性能的浪费,尤其是IO密集型的程序。

异步IO

后面人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程切换带来性能损失。但是这样的坏处也是很大的,主要的坏处就是操作被 “分片” 了,代码写的不是 “一气呵成” 这种。 而是每次来段数据就要判断 数据够不够处理哇,够处理就处理吧,不够处理就在等等吧。这样代码的可读性很低,其实也不符合人类的习惯。

最终有人提出解决方案:协程

协程可以很好解决这个问题。比如 把一个IO操作写成一个协程。当触发IO操作的时候就自动让出CPU给其他协程。要知道协程的切换很轻的。协程通过这种对异步IO的封装 既保留了性能 也保证了代码的容易编写和可读性。在高IO密集型的程序下很好。但是高CPU密集型的程序下没啥好处。也说明了高IO密集型的服务适合协程。
协程诞生解决的是低速IO和高速的CPU的协调问题,解决这类问题主要有三个有效途径:

  • 异步非阻塞网络编程(libevent、libev、redis、Nginx、memcached这类)
  • 协程(golang、gevent)
  • “轻量级线程”,相当于是在语言层面做抽象(Erlang)

对比之下协程的编程难度较低,不要求编程人员要有那么高的抽象思维能力。再加上golang在这方面优秀的实践,协程目前的前途还是一片光明的。

为什么不用异步回调

正常人的思维基本是线性的,当然可以做一些局部的分支判断,但你的思维只能跟着其中的一个分支连贯的走下去。异步回调最大的问题就是破坏掉了人类这种线性的思维模式,你必须把一个逻辑上线性的过程切分成若干个片段,每个片段的起点和终点就是异步事件的完成和开始。固然经过一些训练你可以适应这种思维模式,但你还是要付出额外的心智负担。与人类的思维模式相对应,大多数流行的编程语言都是命令式的,程序本身呈现出一个大致的线性结构。异步回调在破坏点思维连贯性的同时也破坏掉了程序的连贯性,让你在阅读程序的时候花费更多的精力。这些因素对于一个软件项目来说都是额外的维护成本,所以大多数公司并不是很青睐node.js或者RxJava之类的异步回调框架,尽管这些框架能提升程序的并发能力。

举例子

为了明白协程到底解决了什么问题, 需要先弄清楚线程存在什么问题:先考虑一个最简单的web server, 服务线程数量等于处理器数量, 然后把每个请求放在一个线程中同步处理, 显然这样不能充分利用CPU, 因为处理一个请求可以大致分为IO和计算两部分, 处理的IO的时候不需要占用CPU但还是占用了数量有限的线程. 为了能够提高CPU利用率, 我们可以考虑两个办法:

  • 增加服务线程数量, 对每个请求创建一个线程(或者从线程数量不设限的线程池中取线程), 处理IO的时候线程会进入sleep状态, 不会占用CPU, 这样可以利用系统的线程调度能力, 让所有处理器都在处理请求的计算部分.服务线程数量固定, 但IO部分异步处理, 只有处理计算部分的时候才把请求交给数量有限的服务线程,这种方式优点是处理请求还可以使用同步逻辑, 缺点是线程太多时线程调度开销太大, 影响server性能. 因为有多少并发量就需要多少线程, 系统需要保证线程调度的公平性, 线程越多, 线程上下文切换次数就越多, 切换开销就越大. 所以这种方式实际也没有应用。
  • 第二种方式服务线程数量固定,但IO部分异步处理, 只有处理计算部分的时候才把请求交给数量有限的服务线程,优点是高性能, 缺点是需要把同步处理最符合直觉的任务改成异步处理. 当然做web server我们有大量现成的库已经做好了IO异步处理的逻辑. 但web server只是使用多线程的场景之一, 有些任务改成异步处理会很麻烦. 而且同步处理改异步处理本质上是基础设施不够强大, 需要改变应用层的逻辑去优化性能.

这两种方式各有优缺点,而协程解决的问题就是组合这两种方式的优点, 避免它们的缺点,协程处理的任务使用同步逻辑,而且可以有多少并发量就创建多少个协程。所以有些领域用协程比线程会合适. 那么为什么把协程说成轻量级的线程不合适? 协程是否存在协程越多调度开销越大的情况?简单来说一个协程更像是一个任务, 而不是一个持续执行代码的处理器, 系统不需要保证协程调度的公平性,只有在特定的时机才会从一个协程切换到另一个协程, 比如一些协程执行完的时候/主动出让CPU的时候/进入阻塞状态的时候等等, 这样增加协程数量就不会导致增加单位时间内协程切换次数, 所以可以创建大量的协程, 不用去考虑任务调度的开销。

协程和线程差异

协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,我们可以把一个协程 切换到 另一个协程。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。

  2. 线程进程都是同步机制,而协程则是异步

  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态

协程实现

协程的问题但是协程有一个问题,就是系统并不感知,所以操作系统不会帮你做切换。那么谁来帮你做切换?让需要执行的协程更多的获得CPU时间才是问题的关键。笔者知道协程的实现相关的目前的协程框架一般都是设计成 1:N 模式。所谓 1:N 就是一个线程作为一个容器里面放置多个协程。那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,也就是每个协程池里面有一个调度器,这个调度器是被动调度的。意思就是他不会主动调度。

  • 当一个协程发现自己执行不下去了(比如异步等待网络的数据回来,但是当前还没有数据到),这个时候就可以由这个协程通知调度器,这个时候执行到调度器的代码,
  • 调度器根据事先设计好的调度算法找到当前最需要CPU的协程。切换这个协程的CPU上下文把CPU的运行权交个这个协程,直到这个协程出现执行不下去需要等等的情况,或者它调用主动让出CPU的API之类,触发下一次调度。对的没错就是类似于 领导人模式。

那么这个实现有没有问题?其实是有问题的,假设这个线程中有一个协程是CPU密集型的他没有IO操作,也就是自己不会主动触发调度器调度的过程,那么就会出现其他协程得不到执行的情况,所以这种情况下需要程序员自己避免。这是一个问题,假设业务开发的人员并不懂这个原理的话就可能会出现问题。

参考博客

为什么觉得协程是趋势?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值