程序员应如何理解高并发中的协程

来源 | 码农的荒岛求生
责编 | 晋兆雨
头图 | 付费下载于视觉中国

作为程序员,想必你多多少少听过协程这个词,这项技术近年来越来越多的出现在程序员的视野当中,尤其高性能高并发领域。当你的同学、同事提到协程时如果你的大脑一片空白,对其毫无概念。。。

那么这篇文章正是为你量身打造的。

话不多说,今天的主题就是作为程序员,你应该如何彻底理解协程。

普通的函数

我们先来看一个普通的函数,这个函数非常简单:

def func():print("a")print("b")print("c")

这是一个简单的普通函数,当我们调用这个函数时会发生什么?

  1. 调用func

  2. func开始执行,直到return

  3. func执行完成,返回函数A

是不是很简单,函数func执行直到返回,并打印出:

a
b
c

So easy,有没有,有没有!

很好!

注意这段代码是用python写的,但本篇关于协程的讨论适用于任何一门语言,因为协程并不是一种语言的特性。而我们只不过恰好使用了python来用作示例,因其足够简单。

那么协程是什么呢?

从普通函数到协程

接下来,我们就要从普通函数过渡到协程了。

和普通函数只有一个返回点不同,协程可以有多个返回点

这是什么意思呢?

void func() {print("a")暂停并返回print("b")暂停并返回print("c")
}

普通函数下,只有当执行完print("c")这句话后函数才会返回,但是在协程下当执行完print("a")后func就会因“暂停并返回”这段代码返回到调用函数。

有的同学可能会一脸懵逼,这有什么神奇的吗?我写一个return也能返回,就像这样:

void func() {print("a")returnprint("b")暂停并返回print("c")
}

直接写一个return语句确实也能返回,但这样写的话return后面的代码都不会被执行到了

协程之所以神奇就神奇在当我们从协程返回后还能继续调用该协程,并且是从该协程的上一个返回点后继续执行。

这足够神奇吧,就好比孙悟空说一声“定”,函数就被暂停了:

void func() {print("a")定print("b")定print("c")
}

这时我们就可以返回到调用函数,当调用函数什么时候想起该协程后可以再次调用该协程,该协程会从上一个返回点继续执行。

Amazing,有没有,集中注意力,千万不要翻车。

只不过孙大圣使用的口诀“定”字,在编程语言中一般叫做yield(其它语言中可能会有不同的实现,但本质都是一样的)。

需要注意的是,当普通函数返回后,进程的地址空间中不会再保存该函数运行时的任何信息,而协程返回后,函数的运行时信息是需要保存下来的,那么函数的运行时状态到底在内存中是什么样子呢,关于这个问题你可以参考这里。

接下来,我们就用实际的代码看一看协程。

Show Me The Code

下面我们使用一个真实的例子来讲解,语言采用python,不熟悉的同学不用担心,这里不会有理解上的门槛。

在python语言中,这个“定”字同样使用关键词yield,这样我们的func函数就变成了:

void func() {print("a")yieldprint("b")yieldprint("c")
}

注意,这时我们的func就不再是简简单单的函数了,而是升级成为了协程,那么我们该怎么使用呢,很简单:

def A():co = func() # 得到该协程next(co)    # 调用协程print("in function A") # do somethingnext(co)    # 再次调用该协程

我们看到虽然func函数没有return语句,也就是说虽然没有返回任何值,但是我们依然可以写co = func()这样的代码,意思是说co就是我们拿到的协程了。

接下来我们调用该协程,使用next(co),运行函数A看看执行到第3行的结果是什么:

a

显然,和我们的预期一样,协程func在print("a")后因执行yield而暂停并返回函数A。

接下来是第4行,这个毫无疑问,A函数在做一些自己的事情,因此会打印:

a
in function A

接下来是重点的一行,当执行第5行再次调用协程时该打印什么呢?

如果func是普通函数,那么会执行func的第一行代码,也就是打印a。

但func不是普通函数,而是协程,我们之前说过,协程会在上一个返回点继续运行,因此这里应该执行的是func函数第一个yield之后的代码,也就是print("b")。

a
in function A
b

看到了吧,协程是一个很神奇的函数,它会自己记住之前的执行状态,当再次调用时会从上一次的返回点继续执行。

图形化解释

为了让你更加彻底的理解协程,我们使用图形化的方式再看一遍,首先是普通的函数调用:

在该图中,方框内表示该函数的指令序列,如果该函数不调用任何其它函数,那么应该从上到下依次执行,但函数中可以调用其它函数,因此其执行并不是简单的从上到下,箭头线表示执行流的方向。

从图中我们可以看到,我们首先来到funcA函数,执行一段时间后发现调用了另一个函数funcB,这时控制转移到该函数,执行完成后回到main函数的调用点继续执行。

这是普通的函数调用。

接下来是协程。

在这里,我们依然首先在funcA函数中执行,运行一段时间后调用协程,协程开始执行,直到第一个挂起点,此后就像普通函数一样返回funcA函数,funcA函数执行一些代码后再次调用该协程,注意,协程这时就和普通函数不一样了,协程并不是从第一条指令开始执行而是从上一次的挂起点开始执行,执行一段时间后遇到第二个挂起点,这时协程再次像普通函数一样返回funcA函数,funcA函数执行一段时间后整个程序结束。

函数只是协程的一种特例

怎么样,神奇不神奇,和普通函数不同的是,协程能知道自己上一次执行到了哪里。

现在你应该明白了吧,协程会在函数被暂停运行时保存函数的运行状态,并可以从保存的状态中恢复并继续运行。

很熟悉的味道有没有,这不就是操作系统对线程的调度嘛,线程也可以被暂停,操作系统保存线程运行状态然后去调度其它线程,此后该线程再次被分配CPU时还可以继续运行,就像没有被暂停过一样。

只不过线程的调度是操作系统实现的,这些对程序员都不可见,而协程是在用户态实现的,对程序员可见。

这就是为什么有的人说可以把协程理解为用户态线程的原因。

此处应该有掌声。

也就是说现在程序员可以扮演操作系统的角色了,你可以自己控制协程在什么时候运行,什么时候暂停,也就是说协程的调度权在你自己手上。

在协程这件事儿上,调度你说了算。

当你在协程中写下yield的时候就是想要暂停该协程,当使用next()时就是要再次运行该协程。

现在你应该理解为什么说函数只是协程的一种特例了吧,函数其实只是没有挂起点的协程而已。

协程的历史

有的同学可能认为协程是一种比较新的技术,然而其实协程这种概念早在1958年就已经提出来了,要知道这时线程的概念都还没有提出来。

到了1972年,终于有编程语言实现了这个概念,这两门编程语言就是Simula 67 以及Scheme。

但协程这个概念始终没有流行起来,甚至在1993年还有人考古一样专门写论文挖出协程这种古老的技术。

因为这一时期还没有线程,如果你想在操作系统写出并发程序那么你将不得不使用类似协程这样的技术,后来线程开始出现,操作系统终于开始原生支持程序的并发执行,就这样,协程逐渐淡出了程序员的视线。

直到近些年,随着互联网的发展,尤其是移动互联网时代的到来,服务端对高并发的要求越来越高,协程再一次重回技术主流,各大编程语言都已经支持或计划开始支持协程。

那么协程到底是如何实现的呢?

协程是如何实现的

让我们从问题的本质出发来思考这个问题。

协程的本质是什么呢?

其实就是可以被暂停以及可以被恢复运行的函数。

那么可以被暂停以及可以被恢复意味着什么呢?

看过篮球比赛的同学想必都知道(没看过的也能知道),篮球比赛也是可以被随时暂停的,暂停时大家需要记住球在哪一方,各自的站位是什么,等到比赛继续的时候大家回到各自的位置,裁判哨子一响比赛继续,就像比赛没有被暂停过一样。

看到问题的关键了吗,比赛之所以可以被暂停也可以继续是因为比赛状态被记录下来了(站位、球在哪一方),这里的状态就是计算机科学中常说的上下文,context。

回到协程。

协程之所以可以被暂停也可以继续,那么一定要记录下被暂停时的状态,也就是上下文,当继续运行的时候要恢复其上下文(状态),那么接下来很自然的一个问题就是,函数运行时的状态是什么?

这个关键的问题的答案就在《函数运行起来后在内存中是什么样子的》这篇文章中,函数运行时所有的状态信息都位于函数运行时栈中。

函数运行时栈就是我们需要保存的状态,也就是所谓的上下文,如图所示:

从图中我们可以看出,该进程中只有一个线程,栈区中有四个栈帧,main函数调用A函数,A函数调用B函数,B函数调用C函数,当C函数在运行时整个进程的状态就如图所示。

现在我们已经知道了函数的运行时状态就保存在栈区的栈帧中,接下来重点来了哦。

既然函数的运行时状态保存在栈区的栈帧中,那么如果我们想暂停协程的运行就必须保存整个栈帧的数据,那么我们该将整个栈帧中的数据保存在哪里呢?

想一想这个问题,整个进程的内存区中哪一块是专门用来长时间(进程生命周期)存储数据的?是不是大脑又一片空白了?

先别空白!

很显然,这就是堆区啊,heap,我们可以将栈帧保存在堆区中,那么我们该怎么在堆区中保存数据呢?希望你还没有晕,在堆区中开辟空间就是我们常用的C语言中的malloc或者C++中的new。

我们需要做的就是在堆区中申请一段空间,让后把协程的整个栈区保存下,当需要恢复协程的运行时再从堆区中copy出来恢复函数运行时状态。

再仔细想一想,为什么我们要这么麻烦的来回copy数据呢?

实际上,我们需要做的是直接把协程的运行需要的栈帧空间直接开辟在堆区中,这样都不用来回copy数据了,如图所示。

从图中我们可以看到,该程序中开启了两个协程,这两个协程的栈区都是在堆上分配的,这样我们就可以随时中断或者恢复协程的执行了。

有的同学可能会问,那么进程地址空间最上层的栈区现在的作用是什么呢?

这一区域依然是用来保存函数栈帧的,只不过这些函数并不是运行在协程而是普通线程中的。

现在你应该看到了吧,在上图中实际上有3个执行流:

  1. 一个普通线程

  2. 两个协程

虽然有3个执行流但我们创建了几个线程呢?

一个线程。

现在你应该明白为什么要使用协程了吧,使用协程理论上我们可以开启无数并发执行流,只要堆区空间足够,同时还没有创建线程的开销,所有协程的调度、切换都发生在用户态,这就是为什么协程也被称作用户态线程的原因所在。

掌声在哪里?

因此即使你创建了N多协程,但在操作系统看来依然只有一个线程,也就是说协程对操作系统来说是不可见的。

这也许是为什么协程这个概念比线程提出的要早的原因,可能是写普通应用的程序员比写操作系统的程序员最先遇到需要多个并行流的需求,那时可能都还没有操作系统的概念,或者操作系统没有并行这种需求,所以非操作系统程序员只能自己动手实现执行流,也就是协程。

现在你应该对协程有一个清晰的认知了吧。

更多阅读推荐

  • 一文聊“图”,从图数据库到知识图谱

  • 云原生应用Go语言:你还在考虑的时候,别人已经应用实践

  • 一文告诉你雾计算与云计算的区别及对物联网的价值!

  • Word自动化排版画图,Python还能这么玩?

  • 从 JDBC 到 Mybatis,看这篇就够了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/515895.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5G边缘计算行业通识:阿里云ENS技术演进之路

近日,阿里云杨敬宇在CSDN阿里云核心技术竞争力在线峰会上进行了《5G基础设施-阿里云边缘计算的技术演进之路》主题演讲,针对5G时代下,行业和技术的趋势、边缘计算产业通识以及阿里云边缘计算从过去到未来的技术演进之路进行分享。 5GAI需求推…

精讲23种设计模式-策略模式~聚合短信服务和聚合支付服务

文章目录一、设计模式1. 为什么需要使用设计模式2. 设计模式的分类3. 什么是策略模式4. 为什么叫做策略模式5. 策略模式优缺点6. 策略模式应用场景7. Spring框架中使用的策略模式二、策略模式~聚合短信服务2.1. 依赖引入2.2. 抽象公共行为接口2.3. 具体策略接口实现类2.4. 策略…

引领开源新风潮,阿里巴巴编程之夏第二期重磅来袭!

“唯有热爱,可抵岁月漫长”。 2020 年 5 月 25 日,阿里巴巴编程之夏(Alibaba Summer of Code,以下简称 ASoC )第二期正式上线,项目规模再度升级,来自开源社区的 Apache Dubbo、Apache RocketMQ…

powerdesigner逆向工程(sql转pdm)

第一步: File -> Reverse Engineer -> Database 第二步 : Using Script Files -> Add Files

安谋中国发布“玲珑”i3i5 ISP处理器,剑指何方?

随着图像视频处理技术的发展和 5G 时代的来临,除了社交等平台外,以图像、视频为载体的内容渗透到各领域,特别是智能安防、AIoT、智能汽车等新兴领域应用。与此同时,人们对其清晰度、图像分辨率有新的扩展需求,对摄像头…

凯度信息之美奖揭晓,数据可视化后有多“性感”?

前言:更多关于数智化转型、数据中台内容可扫码加群一起探讨 阿里云数据中台官网 https://dp.alibaba.com/index (作者:常成) 2019年的“凯度信息之美奖”揭晓了,有很多很有意思的信息可视化作品。很多作品看到的时候…

精讲23种设计模式-基于责任链模式~构建企业级风控系统

文章目录一、责任链1. 责任链基本概念2. 定义3. 关键要点4. 责任链模式优缺点5. 责任链模式类结构图6. 网关权限控制责任链模式二、构建企业级风控系统2.1. 定义公共抽象任务2.2. (失信名单)校验处理类2.3. (信用卡)逾期处理类2.4. (蚂蚁信用积分)处理类2.5. 责任链工厂(第一种…

抓取了《大秦赋》所有数据,我发现了这些秘密

本文由黄勇老师特约供稿学习人数超13万人的<Python入门到实战一卡通>作者网易、360、华为特约Python讲师前言最近大火的电视剧《大秦赋》&#xff0c;给朋友圈的小伙伴都拉回到那个风云激荡的春秋战国时期&#xff0c;大家都在热情的讨论着大秦一统&#xff0c;秦始皇嬴政…

精讲23种设计模式-基于观察者模式~设计异步多渠道群发框架

文章目录一、观察者模式1. 观察者模式基本概念2. 观察者模式的应用场景3. 观察者模式的类图二、设计异步多渠道群发框架2.1. 定义消息观察者抽象接口2.2. 创建观察者2.3. 主题通知所有观察者2.4. 观察者注册2.5. 自定义线程池2.6. 签单通知入口2.6. 异步通知接口测试2.7. 依赖三…

君子动手不动口,阿里云喊你做云上体验官啦!

想要免费搭建云上博客&#xff1f;想要玩转全云端开发&#xff1f;想要挑战AI经典命题&#xff1f;想要7天进阶成为云计算专家&#xff1f;想要初始化你的云原生工程&#xff1f;快来阿里云 Hands-on Labs&#xff01; Hands-on Labs 是阿里云全新推出的云上动手实验室&#x…

我是Redis,MySQL大哥被我害惨了!

来源 | 编程技术宇宙责编 | 晋兆雨头图 | 付费下载于视觉中国我是Redis你好&#xff0c;我是Redis&#xff0c;一个叫Antirez的男人把我带到了这个世界上。说起我的诞生&#xff0c;跟关系数据库MySQL还挺有渊源的。在我还没来到这个世界上的时候&#xff0c;MySQL过的很辛苦&a…

联手友盟+打造云上数据增长“样板间”, 好兔视频成功逆势突围

前言&#xff1a;更多关于数智化转型、数据中台内容可扫码加群一起探讨 阿里云数据中台官网 https://dp.alibaba.com/index &#xff08;作者&#xff1a;友盟&#xff09; “消费升级”是近年来的中国消费市场热门词汇&#xff0c;消费升级的同时也驱动了内容消费升级。在这样…

SpringBoot 使用 Caffeine 本地缓存

文章目录一、本地缓存介绍二、缓存组件 Caffeine 介绍2.1. Caffeine 性能2.2. Caffeine 配置说明2.3. 软引用与弱引用三、SpringBoot 集成 Caffeine 方式一3.1. Maven 引入相关依赖3.2. 配置缓存配置类3.3. 定义实体对象3.4. 定义服务接口类3.5. 定义服务接口实现类3.6. Caffei…

《Istio 从懵圈到熟练:二分之一活的微服务》

作者 | 声东 阿里云售后技术专家 <关注阿里巴巴云原生公众号&#xff0c;回复 排查 即可下载电子书> 《深入浅出 Kubernetes》一书共汇集 12 篇技术文章&#xff0c;帮助你一次搞懂 6 个核心原理&#xff0c;吃透基础理论&#xff0c;一次学会 6 个典型问题的华丽操作…

为了追求更快,CPU、内存、I/O都做了哪些努力?

来源 | 编程技术宇宙责编 | 晋兆雨头图 | 付费下载于视觉中国背景曾经&#xff0c;我面试的时候有两个最怕的。一怕问算法&#xff0c;二怕问高并发。算法这个&#xff0c;自从刷了不少LeetCode&#xff0c;发现还是有套路可循的&#xff0c;虽不敢说算法能力有多强&#xff0c…

神结合!一招玩转K8s和微服务治理

发布会传送门 进入直播间还有好礼等你拿&#xff01; EDAS产品免费试用&#xff1a;https://www.aliyun.com/activity/middleware/edaspromotiononmay 首届云原生编程挑战赛正式开战&#xff01;立即报名瓜分330000现金奖&#xff1a;https://tianchi.aliyun.com/specials/p…

精讲23种设计模式-基于装饰模式~设计多级缓存框架

文章目录一、装饰模式1. 回顾多级缓存基本概念2. 装饰模式基本的概念3. 装饰模式应用场景4. 装饰者模式定义5. 基于Map手写Jvm内置缓存二、手写一级与二级缓存2.1. redis工具类2.2. 实体类2.3. 接口2.4. 数据库脚本2.5. 测试案例2.6. 测试效果分享三、设计多级缓存框架3.1. 缓存…

阿里云EDAS 3.0重磅发布,无侵入构建云原生应用

发布会传送门 进入直播间还有好礼等你拿&#xff01; EDAS产品免费试用&#xff1a;https://www.aliyun.com/activity/middleware/edaspromotiononmay 首届云原生编程挑战赛正式开战&#xff01;立即报名瓜分330000现金奖&#xff1a;https://tianchi.aliyun.com/specials/p…

二维数组的偏移量

数组的偏移量&#xff1a; 数组空间起始位置的偏移值。 公式&#xff1a; 例题&#xff1a; 结合图片分析例题和公式&#xff1a;

Akamai “三驾马车”,如何应对疫情后新场景形态下的新考验?

2020年10月14日&#xff0c;CDN行业领头羊、负责提供安全数字化体验的智能边缘平台Akamai&#xff08;阿卡迈技术&#xff09;发布了其边缘计算、媒体交付和安全方面的产品组合的多项更新。其中在Akamai智能边缘&#xff08;Akamai Intelligent Edge&#xff09;、媒体交付、应…