因为某些原因,我需要写一点Android上的代码,去异步下载一些文件,同时保证UI不卡。
然而,用Kotlin入门写了一些代码以后,愣是卡在协程(Coroutine)这个地方了;下面是我看kotlin官方的一些教程:
Coroutines Overview - Kotlin Programming Language
Coroutines Guide - Kotlin Programming Language
看完之后一头雾水,主要表现在
经过这么一折腾,我已经忘记啥是coroutine了。
这篇文章是参考Wikipedia重新学习和回忆coroutine的一篇手记。
根据Wiki上定义,
协程是能主动让出执行的程序,允许挂起和恢复两种操作。Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.
这个定义就很清楚了,协程有这些重要特征:
什么叫做主动让出执行呢?
举一个例子。
你去菜市场买菜,散装菜要称斤打标签之后才能结账。
到了结账的时候,你发现有一袋子西红柿没有称斤;这时候你和服务员说:
让后面的人先结吧。
这就叫主动让出执行。
在程序执行的时候,经常会遇到类似的情况;就是程序需要从网络下载一些图片啊,或者从硬盘上加载一些文件啊,这些操作和CPU操作比起来,都是很耗时间的工作;程序就可以使用挂起(suspend)原语,告诉操作系统,我要干一个很长时间的事儿,让其他人先用CPU吧。这就是程序上的主动让出执行。
由于这种方式有点“商量着来”的意思,所以在操作系统里, 也叫做“协作性调度”(Cooporative multitask)。
与“协作性调度”相对应的,叫做“抢占式调度”(Preemptive multitask)。
加入你是个比较“独”的人,买菜时候不管后面人排队,和服务员说:
我还有一袋子西红柿没有称,让后面人等会儿,我称完回来
于是骂骂咧咧的去称西红柿去了。
五分钟过去,等你回来,发现服务员根本没搭理你,主动把你的菜摆在一边,帮后面的人称东西了。这种大快人心的方式,就叫做“抢占式调度”。
抢占式调度,是现代操作系统调度的默认方式;只要程序一干点跟CPU没关系的事情,立刻就会被赶走,让其他需要用CPU的程序使用。
回到协程,我们就可以回答一点“线程和协程有啥区别”的问题了:
这种调度方式,说明协程提供了并发(concurrency),而不提供并行(paralleism)。这两个名词中文翻译很相似,但是他俩是完全不同的概念:
协程之间的切换是非常确定的,就是在“挂起”(Suspend)和“恢复”(Resume)的时候去切换。因为这个切换是提前在程序里写好的,所以切换是可以预测的,而且消耗资源很少。
对比线程,线程的切换是不确定的;一个线程是否被切换,取决于操作系统的“心情”:是不是有优先级更高的事情发生了,是不是当前程序用CPU太久了,等等。所以要想写一个正确的多线程程序,必须使用一些重量级的同步工具,比如信号量(Semaphore)等;这些线程上下文的切换和同步工具的使用,让线程切换的开销要远远高于协程。
最后总结一下协程和线程的区别:
下一篇文章讲讲和协程生成器Generator的区别。