系统中有多个任务同时存在称之为“并发”,并发设计已然成为大规模集群框架的必要特征,本文简单的介绍Scala和golang的并发模型的设计,重点在于比较Scala和Golang在并发实现上的差异。

一、Scala和Golang的并发实现原理

Scala语言并发设计采用Actor模型,采用的是Akka Actor模型库,主要特征如下:

  • “一切皆是参与者”,且各个actor间是独立的;
  • 发送者与已发送消息间解耦,这是Actor模型显著特点,据此实现异步通信;
  • actor是封装状态和行为的对象,通过消息交换进行相互通信,交换的消息存放在接收方的邮箱中;
  • actor可以有父子关系,父actor可以监管子actor,子actor唯一的监管者就是父actor;
  • 一个actor就是一个容器,它包含了状态、行为、一个邮箱(邮箱用来接受消息)、子actor和一个监管策略;
    Go语言与其他语言不同的是,它从语言层面上就支持了并发,Go更提倡“以通信来共享内存,而非以共享内存来通信”,主要特征如下:
  • goroutine(协程,Go的轻量级线程)是Go的轻量级线程管理机制;
  • 通过管道(channel)来存放消息,channel在goroutine之间传递消息,可以缓存;

二、Scala和Golang在并发实现模型上的主要差异:

  • actor是异步的,因为发送者与已发送消息间实现了解耦;而channel则是某种意义上的同步,比如channel的读写是有关系的,期间会依赖对方来决定是否阻塞自己;
  • actor是一个容器,使用actorOf来创建Actor实例时,也就意味着需指定具体Actor实例,即指定哪个actor在执行任务,该actor必然要有“身份”标识,否则怎么指定呢?!而channel通常是匿名的, 任务放进channel之后你不用关心是哪个channel在执行任务;

三、具体实现

说明:对1-10000的整数进行累加,把1-10000平均划分为四部分,启动四个线程进行并发计算,之后将四个线程的运行结果相加得最终得累加值。

Scala实现

Scala定义两个Actor实例,MasterActor和SumActor,前者是后者的父actor,父actor可以监管子actor,子actor唯一的监管者就是父actor。

val sumActor = context.actorOf(Props[SumActor]
                  .withRouter(RoundRobinPool(nrOfInstances = 4)), name = "sumActor")

主程序向MasterActor发送计算指令,然后在线程池中初始化子actor,一共有四个SumActor参与并发计算。

启动计算actor,每个计算线程都会调用计算方法,该方法将处理分段的整数累加,并返回分段累加值给父actor MasterActor。

在整个运算过程中,我们很容易理解发送者与已发送消息间的解耦特征,发送者和接受者各种关心自己要处理的任务即可,比如状态和行为处理、发送的时机与内容、接收消息的时机与内容等。我们可以将更多的精力放在设计上,将各个actor的功能及关联关系表述清楚,剩余的代码实现就非常容易。

Golang实现

Go语言的实现并发功能主要依赖goroutine和channel,首先创建一个计数用的channel,随后发起四个并发计算的协程,

每个计算协程调用计算方法进行分段计算,分段计算完成之后,都会往channel中传入一个计数标志,在率先进入的计数标志没有被读取之前,其他协程在计算结束之后,不能往channel中加入计数标志,只能等待,如此往复,直到channel中的所有计数标志全被取出,意味着所有协程全部处理完毕,在所有的分段计算结束后,就可以计算总的累加值

四、对比Scala和Golang

  • 拿Scala和Go的并发效率来对比,应该是没什么意义的,其间要受到各自内部实现、类型系统、内存使用机制、并发模式、并发规模以及硬件支持等等复杂因素的影响。
  • Akka的actor是解耦的、相对独立的,定义好各个actor间如何沟通,剩下的东西就尽管交给它们处理好了,它们自会按既定方式各司其职,而且每个actor“麻雀虽小五脏俱全”,这也是其解耦性做得好的必然基础。Go语言则独辟蹊径,通过goroutine和channel,以更轻量级的协程方式来处理并发,虽说是更轻量,但你仍得花点心思关注下channel的状态,别一不小心阻塞了,特别是channel数量多逻辑复杂的情况。
  • Akka的Actor实现是库的形式,其也能应用于Java语言。Go语言内嵌了协程的并发实现。
  • Akka基于JVM,实现模式是面向对象,天然讲究抽象与封装,虽然可以穿插混合应用函数式风格。而Go语言显然体现了命令式语言的风格,在需要考虑封装性的时候,需要开发者多着墨。
  • Go语言中轻量级的协程可以轻易启动数十上百万个,这对Scala来说当然是有压力的。但相较而言,Go语言的普及及应用程度尚远不及Java生态,我也希望更多的应用能够实践Go语言。此外,从代码简洁程度来看,Go语言应该会更简洁些吧。
相关文章