Java语言里解决并发问题靠的是多线程,但线程是个重量级对象,不能频繁的创建、销毁,而且线程切换的成本也很高,Java SDK中的线程池及工具类也比较复杂。
其他语言有一种轻量级线程-协程(Coroutine)的并发问题解决方案。
Java OpenSDK中Loom项目的目标是支持协程。
从操作系统的角度看,线程是在内核态中调度的,而协程是在用户态调度的,相对于线程来说,协程切换的成本更低。协程的栈相比线程栈也小得多。从时间维度到空间维度,协程都比线程轻量。
Golang中的协程
1 | import ( |
从示例代码可见,要让hello()方法在一个新的协程中执行,只需要go hello(“World”)这一行代码就搞定了。
利用协程能够很好的实现Thread-Per-Message中介绍的Thread-Per-Message模式。
1 | import ( |
示例代码用Golang实现echo程序的服务端,使用Thread-Per-Message模式,为每个成功建立连接的socket分配一个协程。
利用协程实现同步
在Java里使用多线程并发的处理I/O,基本上用的都是异步非阻塞模型,这种模型的异步主要是靠注册回调函数实现的,不能使用同步处理。同步意味着等待,线程等待本质上是一种严重的浪费。但是对于协程来说,等待的成本不高,基于协程实现同步非阻塞是一个可行的方案。
1 | -- 创建socket |
OpenResty里实现cosocket是一种同步非阻塞方案,借助cosocket可以用线性的思维模式来编写非阻塞的程序(线性的思维模式反映到编程世界里就是同步,异步的思维模式不宜理解)。
示例代码是用cosocket实现的socket程序的客户端,建立连接、发送请求、读取响应所有的操作都是同步的,由于cosocket本身是非阻塞的,所以这些操作虽然是同步的,但是并不会阻塞。
结构化并发编程
golang中的go语句让协程用起来十分简单。Golang中的go语句只是一种快速创建协程的方法而已。但是goto语句会让程序变得混乱-代码的书写顺序和执行顺序不一致,干扰对代码的理解。
艾薇格·迪克斯切(Edsger Dijkstra)首先发现goto语句是毒药,同时提出了结构化程序设计:在结构化程序设计中,可以使用三种基本控制结构来代替goto-顺序结构、选择结构、循环结构。
这三种基本的控制结构奠定了高级语言的基础,它们的入口和出口只有一个,意味着它们是可组合的,而且组合起来一定是线程的,整体来看,代码的书写顺序和执行顺序也是一致的。
开启新的线程(或协程)异步执行这种做法比较粗糙,违背了结构化程序设计。当开启一个新的线程时,程序会并行的出现两个分支,主线程是一个分支,子线程一个分支,这两个分支很多情况下都是天各一方、永不相见。而结构化的程序,可以有分支,但是最终一定要汇聚,不能有多个出口,因为只有这样它们组合起来才是线性的。
总结
计算机里很多面向开发人员的技术,大多数都是在解决一个问题:易用性。协程作为一项并发编程技术,本质上也不过是解决并发工具的易用性问题而已。对于易用性,最重要的是要适应大家的思维模式-用大众的、普通的思维模式写易读的代码,而不是炫技。