1.协程是啥?
协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
我们都知道,线程是CPU调度的基本单位,但是到底设置多少个线程是很难决定的,并且如果一个线程陷入了IO等待的话,会降低整个系统的吞吐量。在Java中,每个线程对应JVM以及操作系统的一个轻量级线程。因为stack的容量是有限的,所以不可能一直生成很多个线程。
协程的本质上其实还是和上面的方法一样,只不过他的核心点在于调度那块由他来负责解决,遇到阻塞操作,立刻yield掉,并且记录当前栈上的数据,阻塞完后立刻再找一个线程恢复栈并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。也就是协程完全是在用户态执行的。
最重要的是,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行) 。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。
2.协程的好处是啥?
- 没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
3.在Java中怎么使用?
-
由于Java的原生语法中并没有实现协程(某些开源框架实现了协程,但是很少被使用)。
-
比较成熟的java三方协程库:Quasar
通过定义Fiber,然后这个Fiber可以被Quasar调度,通过Java7中的fork-join框架。那Quasar是怎么知道啥时候该暂停啥时候该恢复的呢?我们知道 JVM线程中断的条件只有两个,一个是抛异常,另外一个就是return。这里Quasar就是通过抛异常的方式来达到的,抛出SuspendExecution,Quasar就知道去捕捉然后进行调度啦。
4.案例
我们来看一看python当中对协程的实现案例,同样以生产者消费者模式为例:
-
代码中创建了一个叫做consumer的协程,并且在主线程中生产数据,协程中消费数据。
-
其中 yield 是python当中的语法。当协程执行到yield关键字时,会暂停在那一行,等到主线程调用send方法发送了数据,协程才会接到数据继续执行。
-
但是,yield让协程暂停,和线程的阻塞是有本质区别的。协程的暂停完全由程序控制,线程的阻塞状态是由操作系统内核来进行切换。
因此,协程的开销远远小于线程的开销。
5.协程的应用
有哪些编程语言应用到了协程呢?我们举几个栗子:
(1)Lua语言
Lua从5.0版本开始使用协程,通过扩展库coroutine来实现。
(2)Python语言
正如刚才所写的代码示例,python可以通过 yield/send 的方式实现协程。在python 3.5以后,async/await 成为了更好的替代方案。
(3)Go语言
Go语言对协程的实现非常强大而简洁,可以轻松创建成百上千个协程并发执行。
(4)Java语言
如上文所说,Java语言并没有对协程的原生支持,但是某些开源框架模拟出了协程的功能,有兴趣的小伙伴可以看一看Kilim框架的源码: