图解|工作6年多,我还是没有搞懂什么是协程的道与术

发表于 3年以前  | 总阅读数:287 次

前言

大家好,我的朋友们!

大白干了6年多后端,写过C/C++、Python、Go,每次说到协程的时候,脑海里就只能浮现一些关键字yeild、async、go等等。

但是对于协程这个知识点,我理解的一直比较模糊,于是决定搞清楚。

全文阅读预计耗时10分钟,少刷几个小视频的时间,多学点知识,想想就很划算噻!

协程概念的诞生

先抛一个粗浅的结论:协程从广义来说是一种设计理念,我们常说的只是具体的实现

理解好思想,技术点就很简单了,关于协程道与术的区别:

上古神器COBOL

协程概念的出现比线程更早,甚至可以追溯到20世纪50年代,提协程就必须要说到一门生命力极强的最早的高级编程语言COBOL。

最开始我以为COBOL这门语言早就消失在历史长河中,但是我错了。

COBOL语言,是一种面向过程的高级程序设计语言,主要用于数据处理,是国际上应用最广泛的一种高级语言。COBOL是英文Common Business-Oriented Language的缩写,原意是面向商业的通用语言。

截止到今年在全球范围内大约有1w台大型机中有3.8w+遗留系统中约2000亿行代码是由COBOL写的,占比高达65%,同时在美国很多政府和企业机构都是基于COBOL打造的,影响力巨大。

时间拉回1958年,美国计算机科学家梅尔文·康威(Melvin Conway)就开始钻研基于磁带存储的COBOL的编译器优化问题,这在当时是个非常热门的话题,不少青年才俊都扑进去了,包括图灵奖得主唐纳德·尔文·克努斯教授(Donald Ervin Knuth)也写了一个优化后的编译器。

看看这两位的简介,我沉默了:

梅尔文·康威(Melvin Conway)也是一位超级大佬,著名的康威定律提出者。

唐纳德·尔文·克努斯是算法和程序设计技术的先驱者,1974年的图灵奖得主,计算机排版系统TeX和字型设计系统METAFONT的发明者,他因这些成就和大量创造性的影响深远的著作而誉满全球,《计算机程序设计的艺术》被《美国科学家》杂志列为20世纪最重要的12本物理科学类专著之一。

那究竟是什么问题让这群天才们投入这么大的精力呢?快来看看!

COBOL编译器的技术难题

我们都是知道高级编程语言需要借助编译器来生成二进制可执行文件,编译器的基本步骤包括:读取字符流、词法分析、语法分析、语义分析、代码生成器、代码优化器等

这种管道式的流程,上一步的输出作为下一步的输入,将中间结果存储在内存即可,这在现代计算机上毫无压力,但是受限于软硬件水平,在几十年前的COBOL语言却是很难的。

在1958年的时候,当时的存储还不发达,磁带作为存储器是1951年在计算机中得到应用的,所以那个时代的COBOL很依赖于磁带。

其实,我在网上找了很多资料去看当时的编译器有什么问题,只找到了一条:编译器无法做到读一次磁带就可以完成整个编译过程,也就是所谓的one-pass编译器还没有产生。

当时的COBOL程序被写在一个磁带上,而磁带不支持随机读写,只能顺序读,而当时的内存又不可能把整个磁带的内容都装进去,所以一次读取没编译完就要再从头读。

于是,我脑补了COBOL编译器和磁带之间可能的两种multi-pass形式的交互情况:

  • 可能情况一 对于COBOL的编译器来说,要完成词法分析、语法分析就要从磁带上读取程序的源代码,在之前的编译器中词法分析和语法分析是相互独立的,这就意味着:

  • 词法分析时需要将磁带从头到尾过一遍

  • 语法分析时需要将磁带从头到尾过一遍

  • 可能情况二 听过磁带的朋友们一定知道磁带的两个基本操作:倒带和快进。 在完成编译器的词法分析和语法分析两件事情时,需要磁带反复的倒带和快进去寻找两类分析所需的部分,类似于磁盘的寻道,磁头需要反复移动横跳,并且当时的磁带不一定支持随机读写。

从一些资料可以看到,COBOL当时编译器各个环节相互独立的,这种软硬件的综合限制导致无法实现one-pass编译。

协同式解决方案

在梅尔文·康威的编译器设计中将词法分析和语法分析合作运行,而不再像其他编译器那样相互独立,两个模块交织运行,编译器的控制流在词法分析和语法分析之间来回切换

  • 当词法分析模块基于词素产生足够多的词法单元Token时就控制流转给语法分析
  • 当语法分析模块处理完所有的词法单元Token时将控制流转给词法分析模块
  • 词法分析和语法分析各自维护自身的运行状态,并且具备主动让出和恢复的能力

可以看到这个方案的核心思想在于:

梅尔文·康威构建的这种协同工作机制,需要参与者让出(yield)控制流时,记住自身状态,以便在控制流返回时能从上次让出的位置恢复(resume)执行。简言之,协程的全部精神就在于控制流的主动让出和恢复

这种协作式的任务流和计算机中断非常像,在当时条件的限制下,由梅尔文·康威提出的这种让出/恢复模式的协作程序被认为是最早的协程概念,并且基于这种思想可以打造新的COBOL编译器。

在1963年,梅尔文·康威也发表了一篇论文来说明自己的这种思想,虽然半个多世纪过去了,有幸我还是找到了这篇论文:

https://melconway.com/Home/pdf/compiler.pdf

说实话这paper真是有点难,时间过于久远,很难有共鸣,最后我放弃了,要不然我或许能搞明白之前编译器的具体问题了。

怀才不遇的协程

虽然协程概念出现的时间比线程还要早,但是协程一直都没有正是登上舞台,真是有点怀才不遇的赶脚。

我们上学的时候,老师就讲过一些软件设计思想,其中主流语言崇尚自顶向下top-down的编程思想:

对要完成的任务进行分解,先对最高层次中的问题进行定义、设计、编程和测试,而将其中未解决的问题作为一个子任务放到下一层次中去解决。

这样逐层、逐个地进行定义、设计、编程和测试,直到所有层次上的问题均由实用程序来解决,就能设计出具有层次结构的程序。

C语言就是典型的top-down思想的代表,在main函数作为入口,各个模块依次形成层次化的调用关系,同时各个模块还有下级的子模块,同样有层次调用关系。

但是协程这种相互协作调度的思想和top-down是不合的,在协程中各个模块之间存在很大的耦合关系,并不符合高内聚低耦合的编程思想,相比之下top-down使程序结构清晰、层次调度明确,代码可读性和维护性都很不错。

与线程相比,协作式任务系统让调用者自己来决定什么时候让出,比操作系统的抢占式调度所需要的时间代价要小很多,后者为了能恢复现场会在切换线程时保存相当多的状态,并且会非常频繁地进行切换,资源消耗更大。

综合来说,协程完全是用户态的行为,由程序员自己决定什么时候让出控制权,保存现场和切换恢复使用的资源也非常少,同时对提高处理器效率来说也是完全符合的

那么不禁要问:协程看着不错,为啥没成为主流呢?

  • 协程的思想和当时的主流不符合
  • 抢占式的线程可以解决大部分的问题,让使用者感受的痛点不足

换句话说:协程能干的线程干得也不错,线程干的不好的地方,使用者暂时也不太需要,所以协程就这样怀才不遇了。

其实,协程虽然在x86架构上没有折腾出大风浪,由于抢占式任务系统依赖于CPU硬件的支持,对硬件要求比较高,对于一些嵌入式设备来说,协同调度再合适不过了,所以协程在另外一个领域也施展了拳脚。

协程的雄起

我们对于CPU的压榨从未停止。

对于CPU来说,任务分为两大类:计算密集型和IO密集型

计算密集型已经可以最大程度发挥CPU的作用,但是IO密集型一直是提高CPU利用率的难点。

IO密集型任务之痛

对于IO密集型任务,在抢占式调度中也有对应的解决方案:异步+回调

也就是遇到IO阻塞,比如下载图片时会立即返回,等待下载完成将结果进行回调处理,交付给发起者。

就像你常去早餐店,油条还没好,你和老板很熟悉就先交了钱去座位玩手机了,等你的油条好了,服务员就端过去了,这就是典型的异步+回调。

虽然异步+回调在现实生活中看着也很简单,但是在程序设计上却很让人头痛,在某些场景下会让整个程序的可读性非常差,而且也不好写,相反同步IO虽然效率低,但是很好写,

还是以为异步图片下载为例,图片服务中台提供了异步接口,发起者请求之后立即返回,图片服务此时给了发起者一个唯一标识ID,等图片服务完成下载后把结果放到一个消息队列,此时需要发起者不断消费这个MQ才能拿到下载结果。

整个过程相比同步IO来说,原来整体的逻辑被拆分为好几个部分,各个子部分有状态的迁移,对大部分程序员来说维护状态简直就是噩梦,日后必然是bug的高发地

用户态协同调度

随着网络技术的发展和高并发要求,对于抢占式调度对IO型任务处理的低效逐渐受到重视,终于协程的机会来了。

协程将IO的处理权交给了程序员,遇到IO被阻塞时就交出控制权给其他协程,等其他协程处理完再把控制权交回来。

通过yield方式转移执行权的多个协程之间并非调用者和被调用者的关系,而是彼此平等、对称、合作的关系。

协程一直没有占上风的原因,除了设计思想的矛盾,还有一些其他原因,毕竟协程也不是银弹,来看看协程有什么问题:

  • 协程无法利用多核,需要配合进程来使用才可以在多CPU上发挥作用
  • 线程的回调机制仍然有巨大生命力,协程无法全部替代
  • 控制权需要转移可能造成某些协程的饥饿,抢占式更加公平
  • 协程的控制权由用户态决定可能转移给某些恶意的代码,抢占式由操作系统来调度更加安全

综上来说,协程和线程并非矛盾,协程的威力在于IO的处理,恰好这部分是线程的软肋,由对立转换为合作才能开辟新局面

拥抱协程的编程语言

网络操作、文件操作、数据库操作、消息队列操作等重IO操作,是任何高级编程语言无法避开的问题,也是提高程序效率的关键。

像Java、C/C++、Python这些老牌语言也陆续开始借助于第三方包来支持协程,来解决自身语言的不足。

像Golang这种新生选手,在语言层面原生支持了协程,可以说是彻底拥抱协程,这也造就了Go的高并发能力。

我们来分别看看它们是怎么实现协程的,以及实现协程的关键点是什么。

Python

Python对协程的支持也经历了多个版本,从部分支持到完善支持一直在演进:

  • Python2.x对协程的支持比较有限,生成器yield实现了一部分但不完全
  • 第三方库gevent对协程的实现有比较好,但不是官方的
  • Python3.4加入了asyncio模块
  • 在Python3.5中又提供了async/await语法层面的支持
  • Python3.6中asyncio模块更加完善和稳
  • Python3.7开始async/await成为保留关键字

我们以最新的async/await来说明Python的协程是如何使用的:

import asyncio
from pathlib import Path
import logging
from urllib.request import urlopen, Request
import os
from time import time
import aiohttp

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


CODEFLEX_IMAGES_URLS = ['https://codeflex.co/wp-content/uploads/2021/01/pandas-dataframe-python-1024x512.png',
                        'https://codeflex.co/wp-content/uploads/2021/02/github-actions-deployment-to-eks-with-kustomize-1024x536.jpg',
                        'https://codeflex.co/wp-content/uploads/2021/02/boto3-s3-multipart-upload-1024x536.jpg',
                        'https://codeflex.co/wp-content/uploads/2018/02/kafka-cluster-architecture.jpg',
                        'https://codeflex.co/wp-content/uploads/2016/09/redis-cluster-topology.png']


async def download_image_async(session, dir, img_url):
    download_path = dir / os.path.basename(img_url)
    async with session.get(img_url) as response:
        with download_path.open('wb') as f:
            while True:
                chunk = await response.content.read(512)
                if not chunk:
                    break
                f.write(chunk)
    logger.info('Downloaded: ' + img_url)


async def main():
    images_dir = Path("codeflex_images")
    Path("codeflex_images").mkdir(parents=False, exist_ok=True)

    async with aiohttp.ClientSession() as session:
        tasks = [(download_image_async(session, images_dir, img_url)) for img_url in CODEFLEX_IMAGES_URLS]
        await asyncio.gather(*tasks, return_exceptions=True)


if __name__ == '__main__':
    start = time()

    event_loop = asyncio.get_event_loop()
    try:
        event_loop.run_until_complete(main())
    finally:
        event_loop.close()

    logger.info('Download time: %s seconds', time() - start)

这段代码展示了如何使用async/await来实现图片的并发下载功能。

  • 在普通的函数def前面加async关键字就变成异步/协程函数,调用该函数并不会运行,而是返回一个协程对象,后续在event_loop中执行
  • await表示等待task执行完成,也就是yeild让出控制权,同时asyncio使用事件循环event_loop来实现整个过程,await需要在async标注的函数中使用
  • event_loop事件循环充当管理者的角色,将控制权在几个协程函数之间切换

C++

在C++20引入协程框架,但是很不成熟,换句话说是给写协程库的大佬用的最底层的东西,用起来就很复杂门槛比较高。

C++作为高性能服务器开发语言的无冕之王,各大公司也做了很多尝试来使用协程功能,比如boost.coroutine、微信的libco、libgo、云风用C实现的协程库等。

说实话,C++协程相关的东西有点复杂,后面专门写一下,在此不展开了。

Go

go中的协程被称为goroutine,被认为是用户态更轻量级的线程,协程对操作系统而言是透明的,也就是操作系统无法直接调度协程,因此必须有个中间层来接管goroutine。

goroutine仍然是基于线程来实现的,因为线程才是CPU调度的基本单位,在go语言内部维护了一组数据结构和N个线程,协程的代码被放进队列中来由线程来实现调度执行,这就是著名的GMP模型。

  • G:Goroutine

每个Gotoutine对应一个G结构体,G存储Goroutine的运行堆栈,状态,以及任务函数,可重用函数实体G需要保存到P的队列或者全局队列才能被调度执行。

  • M:machine

M是线程的抽象,代表真正执行计算的资源,在绑定有效的P后,进入调度执行循环,M会从P的本地队列来执行,

  • P:Processor

P是一个抽象的概念,不是物理上的CPU而是表示逻辑处理器。当一个P有任务,需要创建或者唤醒一个系统线程M去处理它队列中的任务。

P决定同时执行的任务的数量,GOMAXPROCS限制系统线程执行用户层面的任务的数量。

对M来说,P提供了相关的执行环境,入内存分配状态,任务队列等。

GMP模型运行的基本过程

  • 首先创建一个G对象,然后G被保存在P的本地队列或者全局队列
  • 这时P会唤醒一个M,M寻找一个空闲的P将G移动到它自己,然后M执行一个调度循环:调用G对象->执行->清理线程->继续寻找Goroutine。
  • 在M的执行过程中,上下文切换随时发生。当切换发生,任务的执行现场需要被保护,这样在下一次调度执行可以进行现场恢复。
  • M的栈保存在G对象,只有现场恢复需要的寄存器(SP,PC等),需要被保存到G对象。

总结

本文通过1960年对COBOL语言编译器的one-pass问题的介绍,让大家看到了协同式程序的最早背景以及主动让出/恢复的重要理念。

紧接着介绍了主流的自顶向下的软件设计思想和协程思想的矛盾所在,并且抢占式程序调度的蓬勃发展,以及存在的问题。

继续介绍了关于IO密集型任务对于提升CPU效率的阻碍,抢占式调度对于IO密集型问题的异步+回调的解决方案,以及协程的处理,展示了协程在IO密集型任务上处理的重大优势。

最后说明了当前抢占式调度+协程IO密集型处理的方案,包括Python、C++和go的语言层面对于协程的支持和实现。

本文特别具体的内容并不多,旨在介绍协程思想及其优势所在,对于各个语言的协程实现细节并未展开。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/MusNf8dXsNWj91yWe7Iynw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237229次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录