关于Golang和JVM中并发模型实现的探讨

lintingte · · 1444 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

说起来貌似有好久没有更新过博客了,主要是因为最近一段时间都在各种看书和看源码,所做的记录大部分也都是属于读书笔记性质,所以就没有整理到博客上来,之后会陆续整理一些东西上来。

引子

话说最近终于决定把之前收藏了很久的 mit-6.824 课程的lab拿出来做一下,这门课最有价值的地方就在于它设计了一些列的lab,让你能够在一定程度可控的工作量的coding之后比较深入地体会到众多分布式程序中所面临的一些列公共的问题以及如何去解决它们。例如,分布式容错、并发、网络底层实现等等。这门课的targeted language是golang。原因自然不说,因为golang的简洁所以非常适合用来替代C++等语言来作为lab的实现语言。

在实现的过程当中,我遇到的一个最主要的问题就是,如何在CPU密集型任务、I/O密集型任务以及充分利用多核CPU提升程序性能上找到一个平衡点。当然,这之中最容易想到的解决方案就是 多线程 。但是由于分布式程序的特殊性,它可能拥有大量的网络I/O或者计算任务。这就不可避免需要将使用同步的方式来抒写异步的情怀,解决方案就是将这些计算或者IO放到新的线程中去做,具体的线程调度交给操作系统来完成(虽然我们可以使用异步IO,但是异步IO由于存在大量的callback,不便于程序逻辑组织,所以这里不考虑直接使用异步IO)。这样有一个问题就在于,这之中会有大量的线程在context中,所以线程的上下文切换的开销不可忽视。如果我们在jvm中实现的话,大量的thread可能会很快耗尽jvm堆的内存,不仅会造成堆溢出,而且增大GC时间和不稳定性。因此,最近我就考察了几种常见的并发编程模型以及其对应常见的实现方式。

常见并发编程模型分类

并发编程模型,顾名思义就是为了解决高并发充分利用多核特性减少CPU等待提高吞吐量而提出的相关的编程范式。目前为止,我觉得比较常见的并发编程模型大致可以分为两类:

  • 基于消息(事件)的活动对象
  • 基于 CSP 模型的协程的实现

其中基于消息(事件)的活动对象的并发模型,最典型的代表就是Akka的actor。actor的并发模型是把一个个计算序列按抽象为一个一个Actor对象,每一个Actor之间通过异步的消息传递机制来进行通讯。这样一来,本来顺序阻塞的计算序列,就被分散到了一个一个Actor中。我们在Actor中的操作应该尽量保证非阻塞性。当然,在akka中actor是根据具体的Dispatcher来决定如何处理某一个actor的消息,默认的dispatcher是ForkJoinExecutor,只适合用来处理非阻塞非CPU密集型的消息;akka中还有另外一些Dispatcher可以用于处理阻塞或者CPU密集型的消息,具体的底层实现用到CachedThreadPool。这两种Dispatcher结合起来,我们便能在jvm上建立完整的并发模型。

基于协程的实现,这里主要的代表就是goroutine。Golang的runtime实现了goroutine和OS thread的M:N模型,因此实际的goroutine是基于线程的更加轻量级的实现,我们便可以在Golang中大量创建goroutine而不用担心昂贵的context swtich所带来的开销。goroutine之间,我们可以通过channel来进行交互。由于go已将将所有system call都wrap到了标准库中,在针对这些systemcall进行调用时会主动标记goroutine为阻塞状态并保存现场,交由scheduler执行。所以在golang中,在大部分情况下我们可以非常安心地在goroutine中使用阻塞操作而不用担心并发性受到影响。

goroutine的这种并发模型有一个非常明显的优势,我们可以简单地使用人见人爱的阻塞编程方式来抒发异步的情怀,只要能合理运用 go 关键字。相比较于akka的actor而言,goroutine的程序可读性更强且更好定位错误。

Java能否做到goroutine这样?

既然goroutine这么好用,那么我们能否基于jdk来实现一套类似goroutine的并发模型库??(这里我在知乎提了一个相关的问题, 详见这里 )很遗憾,如果基于JDK的话,是无法实现的。下面我们来分析一下这个问题的本质。

下面我将goroutine的并发模型定义为以下几个要点:

  • 基于Thread的轻量级协程
  • 通过channel来进行协程间的消息传递
  • 只暴露协程,屏蔽线程操作的接口

首先,我们假设能够在JDK中建立起一套基于已有Thread模型的coroutine机制,并且可以通过调用某些方法来创建coroutine对象,分配coroutine任务并执行。但是JDK中存在许多已有的阻塞操作,而这些阻塞操作的调用会直接让线程被阻塞,这样一来依托于线程的coroutine就会失去重新调度的能力。也许你有很多其他的方法进行设计,但是这里本质问题是 不管你怎么进行设计,你都始终无法摆脱JDK中协程状态和线程状态不统一的情况 。除非做到像Go中一样,所有的阻塞操作均被wrap到协程的层次来进行操作。所以,一旦我们用到JDK中和线程绑定的阻塞API时,那么这种并发模型就基本歇菜了。

那么下面我们来分析一下goroutine的实现原理从而解释为什么Java无法做到goroutine那样的协程。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:lintingte

查看原文:关于Golang和JVM中并发模型实现的探讨

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1444 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传