系统性能调优必知必会
陶辉
智链达CTO、前阿里云高级技术专家
立即订阅
4223 人已学习
课程目录
已更新 6 讲 / 共 34 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 万变不离其宗,性能优化也有章可循
免费
基础设施优化 (5讲)
01 | CPU缓存:怎样写代码能够让CPU执行得更快?
02 | 内存池:如何提升内存分配的效率?
03 | 索引:如何用哈希表管理亿级对象?
04 | 零拷贝:如何高效地传输文件?
05 | 协程:如何快速地实现高并发服务?
系统性能调优必知必会
15
15
1.0x
00:00/00:00
登录|注册

05 | 协程:如何快速地实现高并发服务?

陶辉 2020-05-08
你好,我是陶辉。
上一讲谈到,零拷贝通过减少上下文切换次数,提升了文件传输的性能。事实上高并发服务也是通过降低切换成本实现的,这一讲我们来看看它是如何做到的。
如果你需要访问多个服务来完成一个请求的处理,比如实现文件上传功能时,首先访问 Redis 缓存,验证用户是否登陆,再接收 HTTP 消息中的 body 并保存在磁盘上,最后把文件路径等信息写入 MySQL 数据库中,你会怎么做?
用阻塞 API 写同步代码最简单,但一个线程同一时间只能处理一个请求,有限的线程数导致无法实现万级别的并发连接,过多的线程切换也抢走了 CPU 的时间,从而降低了每秒能够处理的请求数量。
为了达到高并发,你可能会选择一个异步框架,用非阻塞 API 把业务逻辑打乱到多个回调函数,通过多路复用实现高并发,然而,由于业务代码过度关注并发细节,需要维护很多中间状态,不但 Bug 率会很高,项目的开发速度也上不去,产品及时上线存在风险。
如果想兼顾开发效率,又能保证高并发,协程就是最好的选择。它可以在保持异步化运行机制的同时,用同步方式写代码,这在实现高并发的同时,缩短了开发周期,是高性能服务未来的发展方向。
你会发现,解决高并发问题的技术一直在变化,从多进程、多线程,到异步化、协程,面对不同的场景,它们都在用各自不同的方式解决问题。我们就来看看,高并发的解决方案是怎么演进的,协程到底解决了什么问题,它又该如何应用。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《系统性能调优必知必会》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(6)

  • 忆水寒
    优点:
    1、首先协程是比线程更轻量级的对象,在Linux内核来说,线程和进程最终对应的都是task结构。
    2、从操作系统的角度来看,线程是在内核态中调度的,而协程是在用户态调度的,所以相对于线程来说,协程切换的成本更低。
    3、协程可以认为是一种并发编程技术,性能比较高,可用性也比较高。Java中的Loom 项目的目标就是支持协程,像go语言更是天然支持协程。
    在我们项目中没有用到协程,主要还是使用的还是异步回调方式。
    主要原因是:大家不知道协程(接收度比较低,觉得没用过可能会遇到很多坑,万一影响产品稳定性怎么办),而且产品里面已经充斥着大量的回调,没法大规模切换了。
    2020-05-08
    3
  • 那时刻
    使用过go语言里的协程,通过GMP来完成goroutine的调度,简单来说,通过P来绑定内核线程M于协程G。通过老师的讲解,加深了理解。
    2020-05-08
    1
  • 每天晒白牙
    今日得到
    协程:如何快速地实现高并发服务?
    要想实现高并发,一个简单的做法就是多线程,为每个请求分配一个线程来执行。但多线程的方式也是有弊端的,如下:
    1.单个线程消耗内存过多,没有足够的内存去创建几万线程实现并发
    2.切换请求是内核通过切换线程来实现的,线程的切换就会带来上下文的切换,也是会耗费 CPU 资源的

    如何破?
    把本来由内核实现的请求切换工作交给用户态的代码来完成,这样可以降低切换成本和内存占用

    异步编程可以实现用户态的请求切换。异步化依赖 IO 多路复用机制的同时,还需要把阻塞方法改为非阻塞方法

    比如一个线程处理两个请求,请求 1 过来通过异步框架发起异步 IO 读,同时向异步框架注册回调函数。然后切换到请求 2,由异步框架发起异步 IO 读,同样也会注册回调函数。
    最后由异步框架依赖 IO 多路复用机制来检查数据是否就绪,如果数据就绪就通过之前请求注册的回调函数去处理

    异步代码不好写,容易出错,我们项目中用的 vertx 异步框架,我到现在也写不好异步代码。

    协程可以弥补异步框架的不足,其实协程是建立在异步的基础上的,他俩都是使用非阻塞的系统调用与内核交互,把请求切换放到用户态。他俩不同的地方在于,协程把异步化中的两段函数封装成一个阻塞的协程函数。在该函数执行时,由协程框架完成协程之间的切换,协程是无感知的。

    协程是如何完成切换的?
    在用户态完成协程的切换和在内核态完成线程的切换原理类似。
    每个协程有独立的栈,一般占用空间选小于线程的栈,(协程一般是几十 KB,线程是 8MB
    )所以相同的内存空间可以创建更多的协程来处理请求。栈中保存了函数的调用关系、参数和返回值。CPU 中的栈寄存器 SP 指向当前协程的栈,指令寄存器 IP 保存下一条执行的指令的地址。

    在协程 1 切换到协程 2 时要把协程 1 的 SP 和 IP 寄存器的值保存下来,再从内存中找到协程 2 上一次切换前保存的寄存器值,写入到 CPU 的寄存器,这样就完成了协程的切换

    协程是用户态的线程,一个线程可以包含多个协程,要保证协程的切换由用户态代码完成,如果协程触发了线程的切换就会导致该线程上的所有协程都阻塞,因为线程的切换是由内核态完成的

    所以要想使用协程,需要协程的生态是完整的,go 好像是天然支持协程,Java 的协程生态现在应该还不成熟,用的比较少
    2020-05-08
    2
    1
  • 问题大师
    异步化编程通过应用层代码实现了请求切换,降低了切换成本和内存占用空间。
    老师 为什么会降低内存占用空间
    2020-05-08
  • 问题大师
    老师 C10K 用epoll模型实现的 不就是非阻塞线程+多路复用嘛 线程为啥说不能支持几万的并发 应该是可以的吧
    2020-05-08
    1
  • 张华中-Blackc
    协程最大的快感就是同步的写法实现异步回调逻辑,本来该内聚的业务不用分开写了。 c++的goto可以实现简单协程。但go语言的协程更像线程,感觉跟c++的不一样
    2020-05-08
收起评论
6
返回
顶部