当前位置: 首页 > 科技观察

听大佬谈Kotlin中的codeboy:协程

时间:2023-03-15 22:08:43 科技观察

前言本文协程主要基于kotlin,可能参考python,go,但尽量避免使用code,而是尽量使用流行的Let's说说不同语言的协程的开发过程,尽量让大家看得懂。近年来,一些编程语言新贵Go和Kotlin引入了协程的语言特性,使得协程这个看似很陌生的概念频繁的进入大家的视野。为了便于理解,开发者把它当作线程的小弟来对待,即轻量级线程。但是如果真要细说的话,coroutine其实是一个很早就出现的编程概念,甚至比thread出现的还要早,但是就编程语言的地位而言,coroutine是不如thread的,所以对它来说并不是奇怪的是,线头低头叫爸爸。看完我上面的介绍,大家一定很疑惑。你说协程出现得早,有资历,那为什么几十年的编程语言发展成了这个样子?线程出现的晚,但怎么会星星之火点燃编程语言的草原。它是否已成为编程语言中的一个重要概念?再者,一条在协程里混了几十年的咸鱼,怎么突然有了梦想,转身唱起歌来?今天就和大家一起梳理整个协程。发展历程,希望能帮助大家更好的了解协程。协程的出现先来说说协程的历史,以及它是如何变得如此凄惨的。毕竟,悲惨的人生需要一个解释。协程最早诞生于1958年,用于汇编语言(60多年前)。它的完整定义发表于1963年。协程是一个通过恢复和暂停代码执行来实现的协作过程。多任务程序组件。同时,后来出现了线程,随着操作系统的出现,大约在1967年提出了线程。线程作为操作系统调度的最小执行组件,主要用于实现抢占式多任务处理。由于每个人都从事多项任务,所以据说没有人能比另一个人做得更好。此外,协程还有几年的历史。理论上,通过自己的努力,很有可能在编程语言中占据核心地位。但是这个协程的开发,当然一方面要靠自我奋斗,另一方面也要考虑历史进程。70年代、80年代和90年代,是计算机疯狂向小型化、个性化进化的时代。计算机严重依赖操作系统来提供用户交互并挤压CPU的最大性能。然而,操作系统是如何压榨计算机性能的呢?什么?依赖多线程。操作系统跟随个人电脑的普及之后,编程语言自然而然地开始依赖操作系统提供的接口来控制电脑。线程已经成为几乎所有编程语言都无法跳过的一个重要概念,并且一直延续至今。说到这里你可能要问了,大家都在搞多任务,为什么线程可以提高CPU资源利用率,而协程却不能呢?当然还有很多其他原因可以解释,但最本质的原因还是协程和协程。线程是两个具有显着差异的概念。这里就得回过头来说一下什么是合作式多任务,什么是抢占式多任务?这两个任务是同一个概念吗?Cooperativemultitasking:上图展示了寿司制作过程的一部分。我们可以把图中的传送转盘和机器夹爪看成是两个任务,它们共同完成食物的生产。这就是协作式多任务处理。协作式多任务处理需要任务之间相互熟悉才能实现协作。抢占式多任务处理:在喂金鱼的场景中,饲料一掉,所有的金鱼立刻围过来抢食。这里,每条金鱼相当于一个任务线程,是抢占式多任务。但是,抢占式多任务(线程)不需要理解和合作,只需要竞争。上面两张图形象地展示了协作式多任务(coroutine)和抢占式多任务(multithreading)的区别。我们可以发现,协程更适合那些相互熟悉的任务组件,通过密切合作来完成某些任务。协作式多任务处理中的“任务”是一个子程序(可以称为函数)。抢占式多任务中的任务是指可以抢占资源的组件或代码(实际上是线程)。这里的多任务也是多线程。所以,协程和线程本来就是两个截然不同的概念,能力也不同,而线程的能力正好迎合了那个时代的需要。自我奋斗+历史进程是线程成功的主要原因。当然,另一方面,由于协程是基于编程语言层面的概念,没有统一定义的接口,所以不同语言实现后的效果是不一样的,这也会给开发者带来问题.很大的麻烦,不利于它的推广。另一方面,线程通过操作系统的统一接口,定义了大致相同的线程使用方式,保证了不同的编程语言使用线程的方式相同。说到这里,我们总结一下协程发展较早的原因:协程不代表先进生产力的发展要求,不代表先进文化的发展方向,不代表绝大多数开发者的根本利益[手动狗头]。协程在不同的编程语言中有不同的实际表现,非常不利于开发者的理解和使用。以上两点是协程几十年不温不火的原因。我们也看到了,虽然看起来都是在搞多任务,但是协程和线程其实并没有太多的交集。咸鱼翻身虽然协程的协同多任务组件不能提高程序执行效率,似乎也没有广阔的应用前景,但是这个协程,你不能随意否定自己,因为你不会知道什么时候,你会突然被历史的进程所照顾。让我们从线程开始。虽然线程已经成为编程界的一个重要概念,但经过多年的使用,开发者逐渐意识到它的痛点:线程间交互困难(异步代码),回调往往难以使用,大量的回调会使代码难以阅读和理解,最终使项目难以维护。简单的说,就是开发者端线程之间如何更方便的进行交互。协程在这里可以做什么?或许我会重新表达线程的痛点:在开发者这边,线程之间如何更方便的协作。回想一下,我们是如何引入协程的?协作式多任务处理,对吧?还记得上图中转盘和机械手之间的协作吗?我们当时说这两个任务更像是两个函数的协作,但是如果把转盘和机器夹具看成是两个线程呢?在编译器的帮助下,线程被封装成可以暂停和恢复的函数。线程能否按照协程的设计进行协作?我们还是从代码层面开始,看看今天协程是如何使用的。设计一个简单的需求:当社区用户发帖时,需要先从后台验证发帖权限,并请求两个接口,然后我们可能需要尝试依次开启两个线程来完成。普通回调代码:funtryPost(){//先通过接口验证权限(实际是开一个线程,然后等待回调)findUserPermission(user,callback()){publicvoidonSuccess(UserPermissionresponse){//如果回调成功,检查是否有Permissionif(response.hasPermission){//如果有权限,则访问发布接口(同样开启线程,等待回调)postContent(content,callback()){publicvoidonSuccess(Resultresponse){//handlesssuccessfulresponse}publicvoidonFail(){}}}else{//如果没有权限,则...}}publicvoidonFail(){}}}这是一个常见的做法,我们需要两次访问接口,而且只能在回调中进行交互,但实际上代码已经很丑了。如果还有其他逻辑,代码只会更加冗长,难以维护。协程是如何解决这个痛点的?看看协程(kotlin和python)的代码是如何实现这个需求的:kotlin的协程代码//函数由suspend关键字标识,可以被协程调用。有了suspend和resume的能力,其实还是用io线程完成接口请求关键字,可以被协程调用cpu,进入暂停状态,等待请求成功后,再恢复执行)varresponse=tryfindUserPermission()//后台获取用户权限if(response.hasPermission){//有权限就开始发帖(开启线程,放弃cpu,暂停执行,等待恢复)varresponse=post()//handleresponseifneed}}}可以看到在kotlin中,协程将线程中的代码封装成一个可以暂停/恢复的函数,这样多线程之间的交互就是像普通功能一样简单。需要回调。python的协程代码importasyncio//async的关键字表示这个函数可以被协程调用也让cpu出来,进入暂停状态,等待恢复response=awaitfindUserPermission()//如果有权限,postifresponse.hasPermission:res=awaitpostContent()//handleresponseifneedasyncio.run(main())python处理这个一种通过协程的问题,这与kotlin本质上是一致的。我相信你也可以看到协程在不同的语言中表现不同。通过上面的伪代码,我们可以清楚地看到协程可以显着简化线程间协作的复杂度。让我们像编写同步代码一样编写异步代码,这会极大地简化您的逻辑并使您的代码易于维护。那么协程是怎么做到的呢?虽然协程在不同的语言中有所不同,但是原理是一样的。编程语言的编译器通过一些关键字修饰(kotlin中为suspend,python中为async等)函数在编译时根据关键字生成一些线程相关的代码,实现函数的挂起和恢复功能,从而保持线程-编译时生成的相关代码,在开发层面提供类似普通功能的协作方式。因为解决了这个痛点,协程越来越受到开发者的青睐。在编译器的帮助下,协程留下了编译时产生的线程相关代码,开发者可以通过操作协程来达到使用线程的目的,所以现在大家都认为协程是一种轻量级的线程。对于多线程协作,或者异步代码之间的协作,协程并不是唯一的解决方案。在JS中,有promise,在Java中,有RxJava等,它们都是致力于解决异步编程相关的问题。我希望他们能像编写同步代码一样编写异步代码。到目前为止,他们做得很好。综上所述,大家对协程的理解存在很多差异,但是对于我来说,协程可以分为两个阶段来理解:协程最开始只是编程组件,用于解决编程中的一些特殊问题。多任务处理更像是多种功能的结合,协同执行。那时候协程其实更像是一个有pause和resume的函数。但是这种功能似乎并不流行,所以协程在很长一段时间内都是比较小众的。(此时协程和线程的关系不大)现在变成了底层支持多线程的协同多任务组件,很好的解决了线程协同的痛点,逐渐变得越来越多更流行。Coroutines和Threads越来越接近,它们似乎变得更加相似。(现在你可以把协程看成是一种轻量级的线程)协程的发展其实经历了这两个阶段。