| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> linux的进程/线程/协程系列5:协程的发展复兴与实现现状 -> 正文阅读 |
|
[系统运维]linux的进程/线程/协程系列5:协程的发展复兴与实现现状 |
协程的发展复兴与实现现状前言最近学习自动驾驶系统时,碰到协程的概念。进程和线程已经迷了,又来个协程,看了很多资料后决定作总结,概括三者联系和区别,最后归结到协程在自动驾驶中的应用。初级程序员目标是搞清三者概念并应用到实际中,而资深工程师则需要在系统层面考虑三者的性能及实现代价,因为直到如今三者仍是Linux内核和各类编程语言持续更新完善的模块之一,所以理清三者的关系、编程应用和考量性能是进阶程序员的必修课。行文目的,是全面讲解进程/线程/协程这一系列繁复的概念和知识点,尽量做到知识点讲精讲细讲全,甄别模糊概念,同时兼顾源码及编程实现,最后归结到Apollo中的协程实现。 本系列文章分九篇讲解:
本篇摘要:本篇从协程机制的起源、发展史及当前现状介绍协程。第一章介绍协同工作制的起源及发展,协程蛰伏的原因及协同式思想的实际应用;第二章描述协程复兴,通过高并发带来的,引出协程风行的历史机遇;第三章详解协程的两个特点:有栈/无栈、对称/非对称;第四章讲解协程的实现方式,引出Python生成器中的协程思想,最后简述当前协程库现状。1. 协同制的发展史1.1 协同工作制的提出协程这个概念,最近这几年可是相当地流行。然而协程并不是最近才出现的新技术,恰恰相反,协程是一项古老的技术。七十年代,Donald Knuth在他的神作《The Art of Computer Programming》中将Coroutine的提出归于Conway Melvin(Conway’s Law的提出者)。早在1958年,梅尔文·康威(Conway Melvin)在用COBOL编译器做词法分析和语法分析时,便提出了基于让出(yield)/恢复(resume)方法的协同工作机制,并于1963年,发表了一篇论文来说明自己的这种思想,虽然半个多世纪过去了,还是有人有幸找到了这篇论文:《Design of a Separable Transition-Diagram Compiler*》。 以现代眼光来看,高级语言编译实际由多个步骤组合而成:词法解析、语法解析、语法树构建,以及优化和目标代码生成等。编译实质上就是从源程序出发,依次将这些步骤的输出作为下一步的输入,最终输出目标代码。在现代计算机上实现这种管道式的架构毫无困难:只需要依次运行,中间结果存为中间文件或放入内存即可。GCC和Clang编译器,以及ANTLR构建的编译器,都遵循这种设计。 在Conway的设计里,词法和语法解析不再是独立运行的步骤,而是交织在一起。梅尔文·康威提出的编译协同工作机制,其核心思想在于:编译器的控制流在词法和语法解析之间来回切换。当词法模块读入足够多的token时,控制流交给语法分析;当语法分析消化完所有token后,控制流交给词法分析。词法和语法分别独立维护自身的运行状态,并且具备主动让出和恢复的能力。Conway构建的这种协同工作机制,需要参与者“让出(yield)”控制流时,记住自身状态,以便在控制流返回时能从上次让出的位置恢复(resume)执行,而编译器的控制流在词法分析和语法分析之间来回切换。简言之,协程的全部精神就在于控制流的主动让出和恢复。我们熟悉的Subroutines(即子过程调用,在很多高级语言中也称其为函数)可以看作在返回时让出控制流的一种特殊协程,其内部状态在返回时被丢弃了,因此不存在“恢复”这个操作。coroutine就是可以中断并恢复执行的subroutine。在当时条件的限制下,由梅尔文·康威提出的这种让出/恢复模式的协作程序被认为是最早的协程概念,并且基于这种思想可以打造新的COBOL编译器1。 以现在眼光来看,编译器的实现并非必需协程。然而,Conway用协程实现COBOL编译器在当时绝不是舍近求远。首先,从原理上,因为COBOL并不是LL(1)型语法,无法简单构建一个以词法分析为子过程的自动机。其次,当年计算机依赖于磁带存储设备,只支持顺序存储。也就是说,依次执行编译步骤并依靠中间文件通信的设计是不现实的,各步骤必须同步前进。正是这样的现实局限和设计需要,催生了协程的概念。 而线程是源于Windows系统的概念,早期版本的Linux并不支持线程,而协程正好代替,因此协程又被称为“轻量级线程”或“用户态线程”。比较有名的有:GNU Pth和libtask(Go语言的作者之一Russ Cox的作品),但它们并没有得到广泛关注和应用。后来随着Linux线程的出现,协程的工作可以由线程代替完成,协程也蛰伏起来,等待着时机。 1.2 自顶向下,无需协同虽然协程伴随着高级语言诞生,却没有能像子过程那样成为通用编程语言的基本元素。从1963年首次提出到上世纪九十年代,我们在ALOGL、Pascal、C、FORTRAN等主流的命令式编程语言中都没有看到原生的协程支持。协程只稀疏地出现在Simula、Modular-2(Pascal升级版)和Smalltalk等相对小众的语言中。作为一个比子进程更加通用的概念,在实际编程中却没有取代子进程,不得不说出乎意外。但如果结合当时的程序设计思想看,又在意料之中:协程不符合那个时代所崇尚的“自顶向下”的程序设计思想,自然也就不会成为当时主流的命令式编程语言的一部分。 正如面向对象的语言是围绕面向对象的开发理念设计一样,命令式编程语言是围绕自顶向下的开发理念设计的。在这种理念的指导下,程序被切分为一个主程序和大大小小的子模块,每个子模块又可能调用更多子模块。C家族语言的main()函数就是这种自顶向下思想的体现。在这种理念指导下,各模块形成层次调用关系,而程序设计就是制作这些子过程。在“自顶向下”这种层次化的理念下,具有鲜明层次的子过程调用成为软件系统最自然的组织方式,也是理所当然。相较之下,具有执行中让出和恢复功能的协程在这种架构下无用武之地。可以说,自顶向下的设计思想从一开始就排除了对协程的需求。其后的结构化编程思想,更进一步强化了“子过程调用作为唯一控制结构”的基本假设。在这样的指导思想下,协程没有成为当时编程语言的一等公民。 但作为一种易于理解的控制结构,协程的概念渗入到软件设计的许多方面。在结构化编程思想一统天下之时,Knuth曾专门写过一篇《Structured Programming with GOTO》来为GOTO语句辩护。在他列出的几条GOTO可以方便编程且不破坏程序结构的例子中,有一个(例子7b)就是用GOTO实现协程控制结构。相较之下,不用GOTO的“结构化”代码反而失去了良好的结构。当然,追求实际结果的工业界对于学界这场要不要剔除GOTO的争论并不感冒。当时许多语言都附带了不建议使用的GOTO语句,显得左右逢源。2这方面一个最明显的例子就是Java——语言本身预留了goto关键字,而编译器却没提供任何支持,在这场争论中做足了中间派。 总之,协程的思想和当时的主流不符合,抢占式的线程可以解决大部分的问题,让使用者感受的痛点不足,所以协程就这样怀才不遇了。 1.3 协同式思想的应用不过在实践中,协程的思想频繁应用于任务调度和流处理。例如,Unix管道就可以看成是众多命令间的协同操作。当然,管道的现代实现都以pipe()系统调用和进程间的通信为基础,而非简单遵循协程的yield/resume语法。 许多协同式多任务操作系统,也可以看成协程运行系统。说到协同式多任务系统,一个常见的误区是认为协同式调度比抢占式调度“低级”,因为我们所熟悉的桌面操作系统,都是从协同式调度(如Windows 3.2、Mac OS 9等)过渡到抢占式多任务系统的。实际上,调度方式并无高下,完全取决于应用场景。抢占式系统允许操作系统剥夺进程执行权限,抢占控制流,因而天然适合服务器和图形操作系统,因为调度器可以优先保证对用户交互和网络事件的快速响应。当年Windows 95刚推出时,抢占式多任务就被作为一大卖点大加宣传。协同式调度则等到进程时间片用完或系统调用时转移执行权限,因此适合实时或分时等对运行时间有保障的系统。 另外,抢占式系统依赖于CPU的硬件支持。因为调度器需要“剥夺”进程的执行权,就意味着调度器需要运行在比普通进程高的权限上,否则任何“流氓(rogue)”进程都可以去剥夺其他进程了。只有CPU支持了执行权限后,抢占式调度才成为可能。x86系统从80386处理器开始引入Ring机制支持执行权限,这也是为何Windows 95和Linux其实只能运行在80386之后的x86处理器上的原因。而协同式多任务适用于那些没有处理器权限支持的场景,这些场景包括资源受限的嵌入式系统和实时系统。在这些系统中,程序均以协程的方式运行。调度器负责控制流的让出和恢复。通过协程的模型,无需硬件支持,我们就可以在一个“简陋”的处理器上实现多任务系统。许多常见的智能设备,如运动手环,受硬件所限,都采用协同调度架构。此外,自动驾驶平台的多数据流处理融合,以及实时处理需求,天然适合采用协同调度架构。 2. 协程的复兴2.1 高并发带来的问题随着近年来软硬件技术的升级,服务端需要处理的数据流越来越多,这对高并发要求越来越高,而传统的服务端并发以C++异步回调模型为主流,异步回调模型的特性使得业务流程中每一个需要等待IO处理的节点都需要切断业务处理流程、保存当前处理的上下文、设置回调函数,等IO处理完成后再恢复上下文、接续业务处理流程。在一个典型的互联网业务处理流程中,这样的行为节点多达十几个甚至数十个(微服务间的rpc请求、与redis之类的高速缓存的交互、与mysql\mongodb之类的DB交互、调用第三方HttpServer的接口等等);被切割的支离破碎的业务处理流程带来了几个常见的难题:
这些具体的难题综合起来,在工程化角度呈现出的效果就是:代码编写复杂,开发周期长,维护困难,BUG多且防不胜防。而当前火热的自动驾驶领域,主机端需要实时处理来自雷达、摄像头、车辆传感器、用户指令、通信网络和卫星信号等数据流IO,这也与异步回调模型格格不入。 2.2 制衡之道——协程另外,随着网络技术的发展,让抢占式调度对IO型任务处理的低效逐渐受到重视,协程的机会终于来了。早在2007年,Go语言问世之时,内置的协程特性完全屏蔽了操作系统线程的复杂细节,在语言层面原生支持协程,可以说是彻底拥抱协程,这也造就了Go的高并发能力,甚至使Go开发者"只知有协程,不知有线程”。在Galang的带动下,像Java、C/C++、Python这些老牌语言也陆续开始借助于第三方包来支持协程,来解决自身语言的不足。2014年腾讯的微信团队开源了一个C风格的协程框架libco,并在次年的架构师峰会上做了宣讲,使业内都认识到异步回调模式升级为协程模式的必要性,从此开启了C++互联网服务端开发的协程时代。双重作用下,BAT三家旗下的各个小部门、业内很多与时俱进的互联网公司都纷纷自研协程框架,一时呈百花齐放之态。 虽然协程库种类繁多,但是万变不离其宗,协程最根本的特征还是:用户态、轻量级、非抢占。协程没有增加线程的数量,只是在线程的基础上通过分时复用的方式运行多个协程,协程调度由用户自己实现,并且同一时间只能有一个协程在执行。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来时,恢复先前保存的寄存器上下文和栈,直接操作栈且基本没有内核切换的开销,所以上下文的切换非常快。另外,协程的非抢占特性使它可以不加锁的访问全局变量。然而协程并不是银弹,它也没有在与进程/线程的竞争中占得绝对上风,让我们看看它有哪些问题:
综上来说,协程和线程并非矛盾,协程的威力在于IO的处理,恰好这部分是线程的软肋,由对立转换为合作才能开辟新局面。在自动驾驶领域,由于目前主机都有多处理机,所以可以在某处理机使用进程/线程处理突发情况,而其它处理机则使用协程进行IO,充分利用进程和协程结合的优势。 3. 协程的两个特性协程运行于用户态,轻量化,非抢占式等性质,相信大家都已了解,这里解释下容易混淆的两个特性:有栈/无栈与对称/非对称。 3.1 有栈/无栈操作系统为方便内存资源的管理,把内存分为堆和栈。两者最典型的区别在于,堆需要使用者主动管理而栈不需要,栈则由负责执行程序的进程/线程/协程管理,所以对栈的处理是进程/线程/协程必不可少的环节。栈分为执行栈和回调栈,无论进程、线程还是协程,运行时都需要执行栈保存参数、局部变量等数据。但区分协程的栈指的是回调栈(Callback Stack,有的文章翻译为调用栈,调用堆栈,为避免歧义,作者称其为回调栈),就是函数调用过程中对局部变量、回调地址和返回值等的一种保存机制。所谓的有栈/无栈并不是说这个协程运行的时候有没有运行栈,而是指协程之间切换时,是否使用回调栈。而根据协程切换时是否使用回调栈,可以将协程分为两类:有栈协程和无栈协程。有栈协程有自己的回调栈,用来保存切换回来时需要恢复的环境变量,而无栈协程本身不使用回调栈而共用线程执行栈或进程执行栈,下边详细解释。 有栈协程:有栈协程需要操作回调栈,通常包括回调栈的保存和恢复,目前实现方式包括:纯手写汇编实现、glibc库的ucontext.h的swapcontext、C接口的setjmp/longjmp、采用C++标准扩展库Boost.Context的接口jump_fcontext,它们一般会在yield操作时保存回调栈,而在resume操作时恢复回调栈内容,常见的stackfull协程库有ntyco、coroutine、goroutine、libtask、libmill、boost、libco、libgo、tbox等。有栈协程的回调机制,更类似线程的调度,只是调度一个发生在用户态可以由用户控制,一个发生在内核态由系统控制。它们都需要一个压栈和出栈的操作,用来保存和恢复相关上下文(其实主要就是寄存器状态),这样就可以保证整个运行期间变量的有效性和安全性。 无栈协程:无栈协程的本质就是一个状态机(state machine),其切换本质可以理解为寄存器的指针的改变。因为无栈协程的栈内保存的不是数据而是指向数据的指针,协程的所有数据保存在堆上。正因为如此,所以协程根本不需要上下文切换,因为全局的上下文就没变过,改变他们的调用关系就可以。 下面举两个例子,更能说明这个问题。首先是有栈协程libco的创建和切换:
这段代码中,主协程开启一个协程去执行test函数,在test中我们需要两次从协程中切换出去,这里对应了两个sleep操作,sleep所做的事情就是把当前协程的CPU执行权切换到回调栈的上一层,并在超时或注册的fd就绪时返回(当然样例这里就只是超时了)。那么无栈协程跑相同的代码是怎么样的呢?其实就是翻译成类似于以下代码:
可以看到,与有栈协程中的test函数相比,这里把整个协程抽象成一个类,函数MoveNext以原来需要执行切换的语句处为界限,把函数test划分为几个部分,并在某部分执行完后进行状态转移(改变_state),下一次调用MoveNext时就会执行下一部分,这样就不需要像有栈协程那样显式的执行上下文切换了,只需要一个简易的调度器(_state)来调度即可。从执行栈的角度看,其实所有的协程共用的都是一个栈,即进程栈/协程执行栈,也可称为系统栈,这时就不需要给协程分配回调栈,同时因为是函数调用,当然也不必显示保存寄存器的值。另外,相比有栈协程把局部变量放在回调栈上,无栈协程直接使用系统栈使得CPU cache的局部性更好,同时也使无栈协程的中断和函数返回几乎没有区别,这样也凸显出无栈协程的高效。无栈协程将subroutine与函数的区别进一步简化为可中断后返回。 总结:有栈协程涉及到对于回调栈中寄存器的保存和修改,也涉及到对每个协程的执行栈分配。现代寄存器基本都是上百个字节数据,虽然保存和恢复的数据量不大,但仍然不可避免的加大整个系统的开销,特别是大数量协程时,有栈协程在效率上势必有一些损失。但由于无栈协程无法解决异步回调模型中上下文保存和恢复问题,功能相对单一,并且有栈协程可采用共享栈来减少上下文切换时的性能损失,所以大部分协程库均为有栈协程。然而简化编程是大方向,不知以后是否会出现复杂功能的无栈协程库,请拭目以待。对此部分还不理解的读者可参考文献4和文献5。 3.2 对称/非对称协程在执行过程中,可以调用别的协程自己则中途退出执行,之后又从调用别的协程的地方恢复执行,这有点像操作系统的线程:执行过程中可能被挂起,让位于别的线程执行,稍后又从挂起的地方恢复执行。在这个过程中,协程与协程之间实际上不是普通“调用者与被调者”的关系,他们之间的关系根据恢复的方式不同,分为对称协程(symmetric coroutines)和非对称协程(asymmetric coroutines)。 具体来讲,非对称协程(asymmetric coroutines)是跟一个特定的调用者绑定的,协程让出CPU时,只能让回给原调用者。那到底是什么东西“不对称”呢?第一,非对称在于程序控制流转移到被调协程时使用的是suspend/resume操作,而当被调协程让出 CPU 时使用的却是return/yield操作。第二,协程间的地位也不对等,caller与callee关系是确定的,不可更改的,非对称协程只能返回最初调用它的协程。微信团队的libco其实就是一种非对称协程,Boost C++库也提供了非对称协程。另外,挂起(suspend)和恢复(resume)跟yield的区别是:yield后的协程,之后还会被切换回来,但是被suspend挂起的协程,除非调用resume()恢复它,否则永远不会再被执行到。在不同语言中,这三者会有不同的叫法,比如call也会调用新函数时也会同时实现suspend旧函数的功能,有的语言用yield/resume和return,不一而论,但区别不变。 对称协程(symmetric coroutines)则不同,被调协程启动之后就跟之前运行的协程没有任何关系了。协程的切换操作,一般而言只有一个操作yield或return,用于将程序控制流转移给另外的协程。对称协程机制一般需要一个调度器的支持,按一定调度算法去选择yield或return的目标协程。Go语言提供的协程,其实就是典型的对称协程。不但对称,goroutines还可以在多个线程上迁移。这种协程跟操作系统中的线程非常相似,甚至可以叫做“用户级线程”。 总结:其实对于“对称”这个词,阐述的是协程之间的关系,协程之间人人平等,没有谁调用谁,大家都是一样的,也不需要回调栈,只需要一个数据结构存储所有未执行完的协程即可,一般对应无栈协程。而非对称协程拥有回调栈,协程之间存在明显的调用关系,对应有栈协程。至于如何选择,我认为非对称协程更适合已知的IO密集型应用,可以自主把握切换的时机,而对称协程可能会切到未完成的任务,当然调度算法足够优秀也可以避免,也就是说对称协程对调度算法的要求更严苛。 4. 协程的实现我们前面说过,协程的思想本质上就是控制流的主动让出和恢复机制。在现代语言中,可以实现协程思想的方法很多,这些实现间并无高下之分,所区别的就是是否适合应用场景。理解这一点,我们对于各种协程的分类,如半对称/对称协程、有栈/无栈协程等具体实现就能提纲挈领,无需在实现细节上纠结。 本章内容如下:第一节总述协程的实现方式;第二节了解下协同工作制在python的迭代器和生成器是如何显现;第三节详述当前协程库现状。 4.1 实现方式协程在实践中的实现方式千差万别,一个简单的原因是,协程本身可以通过许多基本元素构建。基本元素的选取方式不一样,构建出来的协程抽象也就有差别。例如,Lua语言选取了create、resume/yield作为基本构建元素,从调度器层面构建出所谓的“非对程”协程系统;而Julia语言绕过调度器,通过在协程内调用yieldto函数完成了同样的功能,构建出了一个所谓的对称协程系统。尽管这两种语言使用了同样的setjmp库,构造出来的原语却不一样。又如,许多C语言的协程库都使用了ucontext库实现,这是因为POSIX本身提供了ucontext库,不少协程实现是以ucontext为蓝本实现的。这些实现,都不可避免地带上了ucontext库的一些基本假设,如协程间是平等的,一般带有调度器来协调协程(如libtask实现,以及云风的coroutine库)。另外,C++的标准扩展库Boost.context经过重新设计,切换效率更高。它主要有两个接口,一个make_fcontext(),一个jump_fcontext()。相比ucontext,boost的切换模式少了单独对context进行保存(getcontext)和切换(setcontext)过程,而是把两者合并到一起,通过jump_fcontext接口实现直接切换,构思确实精妙。Go语言的一个鲜明特色就是通道(channel)作为一级对象。因此,resume和yield等在其他语言里的原语在Go中都以通道方式构建。我们还可以举出诸多近似的例子。其风格差异往往和语言的历史、演化路径、要解决的问题相关,我们不必苛求其协程模型一定要如此这般。 总的来说,协程为协同任务提供了一种运行时抽象。这种抽象非常适合于协同多任务调度和数据流处理。在现代操作系统和编程语言中,因为用户态线程切换代价比内核态线程小,协程成为了一种轻量级的多任务模型。我们无法预测未来,但可以看到,协程已成为许多擅长数据处理语言的一级对象。随着计算机并行性能的提升,用户态任务调度已成为一种标准的多任务模型。在这样的大趋势下,协程这个简单且有效的模型就显得更加引人注目。 然而提到协程,很多人会想到Python的yield生成器,两者究竟存在什么关系?在讲述协程具体实现之前,我们先来欣赏协程思想在动态语言Python中的光华。 4.2 Python的迭代器和生成器编程思想能否普及开来,很大程度上在于应用场景。协程没有能在自顶向下的世界里立足,却在动态语言世界中大放光彩,这里最显著的例子莫过于Python的迭代器和生成器。 回想一下在C的世界里,循环的标准写法是:for (i = 0; i < n; ++i) { … }。这行代码包含两个独立的逻辑:for循环控制了i的边界条件,++i控制了i的自增逻辑。对于STL和复杂数据结构,因为往往只支持顺序访问,循环大多写成:for (i = A.first(); i.hasNext();i = i.next()) { … }。这种设计抽象出了一个独立于数据结构的迭代器,专门负责数据结构上元素的访问顺序。迭代器把访问逻辑从数据结构中分离出来,是一个常用的设计模式(GoF 23个设计模式之一),我们在STL和Java Collection中也常常看到迭代器的身影。在适当的时候,我们可以更进一步引入一个语法糖,将循环写成:for item in A.Iterator() {…}。 事实上,许多现代语言都支持类似的语法。这种语法抛弃了以i变量作为迭代指针的功能,要求迭代器自身能记住当前迭代位置,调用时返回下一个元素。读者不难看到,这就是我们在文章开始提到的语法分析器的架构。正因为如此,我们可以从协程的角度来理解迭代器:当控制流转换到迭代器时,迭代器负责生成和返回下一个元素。一旦准备就绪,迭代器就让出控制流。在Python中,这种特殊的迭代器实现又被成为生成器。以协程角度切入的好处在于设计大大精简。实际上,在Python中,生成器本身就是一个普通函数,和其他普通函数的唯一不同,在于它的返回语句是协程风格的yield。这里,yield一语双关,既是让出控制流,也是生成迭代器的返回值。当然,yield只是冰山的一角,现代的Python语言还充分利用了yield关键字构建yield from语句、(yield)语法等,让我们毫无困难地将协程的思想融入到Python编程中,限于篇幅这里不再展开。 4.3 当前协程库现状简述本节讲述当前各主流语言协程库发展现状,包括C/C++、Python、Java和Go。如果你有关注过C++语言的最新动态,可能也会注意到近几年不断有人在给 C++标准委员会提协程的支持方案,比如底层API的C++标准扩展库Boost中的Context,C++20还引入了不太成熟的协程框架CoroutineTS。此外还有其它第三方库,按上下文切换的不同方式分类,比较出名的有:
除了服务器主流语言C++,其它语言也在积极引入了协程机制。比如C语言中各式各样的协程库,根据上下文切换方法不同,分为以下几种:
其中Protothread最轻,但受限最大,ucontext耗资源性能慢,ntyco和coroutine很久未更新,目前看来tbox和ST是C语言协程库的最佳选择。此外,Java同样有一些试验性的解决方案在不断被提出来,比如有栈协程Coroutines、Quasar,还有最新可能纳入JDK18的JEPs。Python的实现方案有yield生成器、greenlet库、gevent库、asyncio异步协程,以及Python3.7的保留关键字async/await,它属于无栈协程。专为协程而生的Go语言中的Goroutine,被认为是用户态更轻量级的线程,对操作系统不可见,所以goroutine仍然是基于线程实现,因为线程才是CPU调度的基本单位,在go语言内部维护了一组数据结构(如队列)和N个线程的中间层,协程的代码被放进队列中来由线程来实现调度执行,这就是著名的GMP模型(Goroutine,Machine,Processor)。除了语言,还有跨windows平台的ThreadContext库,包括GetThreadContext/SetThreadContext接口,经测试相比linux的汇编实现要慢一些,此外还有Fiber纤程,主要包括CreateFiber/ConvertThreadToFiber/SwitchToFiber接口等,windows平台用户可以去研究下。 本篇到此结束。下一篇我们将分析当前各协程库的优劣势,并给出推荐:libgo,分析其源码目录及部分源代码,最后提引性能神器tbox。 本打算行文尽量简洁,但达不到讲精讲细的目的,所以我对本系列文章的定位是复杂知识点详细总结,在此基础上做到尽量简练。由于查阅了大量资料,耗费了很多精力,虽然谈不上尽善尽美,但也希望各位支持作者一下,来个一键四联(点赞、收藏、评论、转发),希望能帮助不断探索的你。 参考文献 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 3:37:15- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |