本文转载自:https://juejin.im/post/5d5df6b35188252ae10bdf42
什么是协程?
今天要讲的这个东西说实话,我也是今天才知道,一个我们大多数人可能从来都没用过的语法,那就是传说中的【协程 Coroutine】。
可能你会说,携程谁不知道啊,不就是哪个用来订机票订酒店的软件么,这有什么好学的!这样的话你就错了,此协程非彼携程,可不要傻傻分不清楚喽!
什么是进程和线程
进程是什么
直白地讲,进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程,进程拥有代码和打开的文件资源、数据资源、独立的内存空间。
线程又是什么
线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。
线程具有五种状态:
对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。
无论进程还是线程,都是由操作系统所管理的。
线程之间是如何进行协作的呢?
最经典的例子就是生产者/消费者模式:
若干个生产者线程向队列中写入数据,若干个消费者线程从队列中消费数据。
什么是协程
官方定义如下:
1 | A coroutine is a function that can suspend its execution (yield) until the given given YieldInstruction finishes. |
用我蹩脚的英语来翻译一下就是:
学过计算机组成原理的都知道,当 CPU 在多个进程间切换时,那些后台程序就会处于这种暂停用英文的 Suspend 或许更恰当)的状态,所以早年的电脑即使用一个 CPU 也可以同时处理多个进程任务,这是一种“伪多线程”的技术。
除此之外比较重要的一点是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程那样需要上下文切换来消耗资源,因此协程的开销远远小于线程的开销。
注意,这里要划一个重点,协程是一种“伪多线程”,始终记得这一点,可以帮助我们来理解协程会这个概念。
协程函数的写法
Java 语言并没有对协程提供原生支持,所以用 Java 暂时还演示不了,但是有个开源框架基本模拟除了协程的功能,感兴趣的朋友可以去看看源码。。。
地址 :https://github.com/kilim/kilim
(PS:kotlin新版本已对协程有了支持)
Go 语言根据我查询资料来看,对于协程的支持超乎我的想象,可以说是强大而简洁,轻轻松松分分钟创建成百上千个协程并发执行。
1 | func Add(x, y int) { |
如上代码,在一个函数调用前加上 go 关键字,这次调用就会在一个新的协程中并发执行。当被调用的函数返回时,这个协程也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
Python 语言也可以通过 yield/send 的方式实现协程。在 python 3.5 以后,async/await 成为了更好的替代方案。
1 | def consume(): |
其他语言的写法我也就不写了,毕竟不太熟,写了怕误人子弟!!!
总结
根据今天查阅的资料来看,协程的应用场景主要在于 :I/O 密集型任务。
这一点与多线程有些类似,但协程调用是在一个线程内进行的,是单线程,切换的开销小,因此效率上略高于多线程。当程序在执行 I/O 时操作时,CPU 是空闲的,此时可以充分利用 CPU 的时间片来处理其他任务。在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 None )。 有了协程,我们在函数的执行过程中,如果遇到了耗时的 I/O 操作,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回控制权。
简单来讲协程的好处:
- 跨平台
- 跨体系架构
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序:这一点和事件驱动一样,可以使用异步IO操作来解决
最后再贴个图来总结一下,更清楚: