0x00 前言
一般来说有两种策略用来在并发线程中进行通信:共享数据和消息传递。熟悉c和java并发编程的都会比较熟悉共享数据的策略,比如java程序员就会常用到java.util.concurrent
包中同步、锁相关的数据结构。
使用共享数据方式的并发编程面临的最大的一个问题就是数据条件竞争(data race)。处理各种锁的问题是让人十分头痛的一件事。
和共享数据方式相比,消息传递机制最大的优点就是不会产生数据竞争状态(data race)。实现消息传递有两种常见的类型:基于channel的消息传递和基于Actor的消息传递。本文主要是来分享Scala的Actor模型。
文章结构
本篇博客尝试讲解Actor模型。由于目前Scala的使用频率较高,因此主要语言为Scala
主要分为下面几个部分:
- Actor模型的基本概念使用
- 讲一下akka框架中scala的基本使用,主要提几个重要的api
- 写几个例子帮助理解
1). HelloWorld 简单版:通过这个例子来简单看一下Akka中Actor的使用
2). HelloWordl 进阶版:稍微进化了一点点,多了preStart和PoisonPill的使用。
3). WordCount 伪分布式:一个单机版的wordcount,一个map,多个reduce。后续再补充完全分布式的程序。
0x01 基本概念
Actor是计算机科学领域中的一个并行计算模型,它把actors当做通用的并行计算原语:一个actor对接收到的消息做出响应,进行本地决策,可以创建更多的actor,或者发送更多的消息;同时准备接收下一条消息。
在Actor理论中,一切都被认为是actor,这和面向对象语言里一切都被看成对象很类似。但包括面向对象语言在内的软件通常是顺序执行的,而Actor模型本质上则是并发的。
什么是Actor模型
Actor的概念来自于Erlang,在AKKA中,可以认为一个Actor就是一个容器,用以存储状态、行为、Mailbox以及子Actor与Supervisor策略。Actor之间并不直接通信,而是通过Mail来互通有无。
每个Actor都有一个(恰好一个)Mailbox。Mailbox相当于是一个小型的队列,一旦Sender发送消息,就是将该消息入队到Mailbox中。入队的顺序按照消息发送的时间顺序。Mailbox有多种实现,默认为FIFO。但也可以根据优先级考虑出队顺序,实现算法则不相同。
消息和信箱
异步地发送消息是用actor模型编程的重要特性之一。消息并不是直接发送到一个actor,而是发送到一个信箱(mailbox)。如下图。
这样的设计解耦了actor之间的关系——actor都以自己的步调运行,且发送消息时不会被阻塞。虽然所有actor可以同时运行,但它们都按照信箱接收消息的顺序来依次处理消息,且仅在当前消息处理完成后才会处理下一个消息,因此我们只需要关心发送消息时的并发问题即可。
0x02 Akka中的Actor
我们会用到Akka框架提供的Actor,因此在这里先大致介绍一下Akka中的Actor使用方式。
Actor System
Actor System是进入AKKA世界中的一个入口,也可以看做是Actor的系统工厂或管理者,掌控者Actor的生命周期,包括创建、停止Actor,当然也可以关闭整个ActorSystem。
比如我们后面会展示出来的代码:
object BetterHelloWorld extends App{
val system = ActorSystem("HelloActors")
system.actorOf(Props[BetterMaster], "master")
}
Actor的层级
Actor的整个体系就像是一家层级森严的企业组织,层次越高,管理权限与职责就更大。在AKKA中,parent actor就是child actor的supervisior,这意味着parent actor能够掌控child actor的整个生命周期。而这种分级的模式也能够更好地支持系统的容错。
如果要创建child actor,就不再调用ActorSystem的actorOf()方法。
如下BetterMaster
就是一个parent actor,而BetterTalker就是一个child actor,完整代码请看后面的例子。
parent actor:
class BetterMaster extends Actor {
val talker = context.actorOf(Props[BetterTalker], "talker")
override def preStart { ... }
def receive = { ... }
}