Akka(6): become/unbecome:运算行为切换
通过一段时间的学习了解,加深了一些对Akka的认识,特别是对于Akka在实际编程中的用途方面。我的想法,或者我希望利用Akka来达到的目的是这样的:作为传统方式编程的老兵,我们已经习惯了直线流程方式一口气实现完整的功能。如果使用Akka,我们可以把这个完整的功能分切成多个能产生中间临时结果的小功能然后把这些功能放到不同的Actor上分别独立运算,再通过消息来连接这些功能集合成最终结果。如此我们就轻易得到了一个多线程并发程序。由于Akka是软件工具(Tool),没有软件架构(Framework)对编程方式的特别要求,Actor的构建和使用非常方便,我们甚至不需要多少修改就可以直接把原来的一段代码移到Actor上。如果遇到一些重复的运算,我们还可以用Routing来实现并行运算。当然,把Actor当作简单的行令运算器可能还不够,如果能实现一些具体运算之上的高层次程序逻辑和流程就更加完善。我们可以用这样的高层次Actor去解析程序逻辑、执行流程、把具体的运算分配给其它各种运算Actor或者一组Routees并行运算从而取得整体程序的高效率运行。具备了这些功能后,也许我们就可以完全用Actor模式来替代传统单线程行令编程了。Akka可以通过Actor的动态行为转换来实现同一Actor在不同情况下提供不同的功能支持。我们前面提到Actor的功能是在receive函数内实现的。那么转换功能是否就是切换不同的receive函数呢?答案是确定的,Akka是通过Actor的context.become(rcvFunc)来实现receive函数切换的,我们看看下面这个示范:
import akka.actor._ object FillSeasons {
case object HowYouFeel
def props = Props(new FillSeasons)
} class FillSeasons extends Actor with ActorLogging {
import FillSeasons._ override def receive: Receive = spring
def Winter: Receive = {
case HowYouFeel =>
log.info("It's freezing cold!")
}
def summer: Receive = {
case HowYouFeel =>
log.info("It's hot hot hot!")
}
def spring: Receive = {
case HowYouFeel =>
log.info("It feels so goooood!")
}
} object Becoming extends App {
val demoSystem = ActorSystem("demoSystem") val feelingsActor = demoSystem.actorOf(FillSeasons.props,"feelings") feelingsActor ! FillSeasons.HowYouFeel }
在FeelingsActor里我们定义了三个receive函数,对共同的HowYouFeel消息采取了不同的反应。默认行为是spring。那么应该如何在三种行为中切换呢?用context.become(???),如下:
import akka.actor._ object FillSeasons {
case object HowYouFeel
case object ToSummer
case object ToSpring
case object ToWinter
def props = Props(new FillSeasons)
} class FillSeasons extends Actor with ActorLogging {
import FillSeasons._ override def receive: Receive = spring
def winter: Receive = {
case HowYouFeel =>
log.info("It's freezing cold!")
case ToSummer => context.become(summer)
case ToSpring => context.become(spring) }
def summer: Receive = {
case HowYouFeel =>
log.info("It's hot hot hot!")
case ToSpring => context.become(spring)
case ToWinter => context.become(winter)
}
def spring: Receive = {
case HowYouFeel =>
log.info("It feels so goooood!")
case ToSummer => context.become(summer)
case ToWinter => context.become(winter)
}
} object Becoming extends App {
val demoSystem = ActorSystem("demoSystem") val feelingsActor = demoSystem.actorOf(FillSeasons.props,"feelings") feelingsActor ! FillSeasons.HowYouFeel
feelingsActor ! FillSeasons.ToSummer
feelingsActor ! FillSeasons.HowYouFeel
feelingsActor ! FillSeasons.ToWinter
feelingsActor ! FillSeasons.HowYouFeel
feelingsActor ! FillSeasons.ToSpring
feelingsActor ! FillSeasons.HowYouFeel scala.io.StdIn.readLine()
demoSystem.terminate()
}
我们增加了三个消息来切换receive。运算结果如下:
[INFO] [// ::46.013] [demoSystem-akka.actor.default-dispatcher-] [akka://demoSystem/user/feelings] It feels so goooood!
[INFO] [// ::46.019] [demoSystem-akka.actor.default-dispatcher-] [akka://demoSystem/user/feelings] It's hot hot hot!
[INFO] [// ::46.028] [demoSystem-akka.actor.default-dispatcher-] [akka://demoSystem/user/feelings] It's freezing cold!
[INFO] [// ::46.028] [demoSystem-akka.actor.default-dispatcher-] [akka://demoSystem/user/feelings] It feels so goooood! Process finished with exit code
就这样在几个receive里窜来窜去的好像已经能达到我们设想的目的了。看看Akka源代码中become和unbecome发现这样的做法是不正确的:
def become(behavior: Actor.Receive, discardOld: Boolean = true): Unit =
behaviorStack = behavior :: (if (discardOld && behaviorStack.nonEmpty) behaviorStack.tail else behaviorStack) def become(behavior: Procedure[Any]): Unit = become(behavior, discardOld = true) def become(behavior: Procedure[Any], discardOld: Boolean): Unit =
become({ case msg ⇒ behavior.apply(msg) }: Actor.Receive, discardOld) def unbecome(): Unit = {
val original = behaviorStack
behaviorStack =
if (original.isEmpty || original.tail.isEmpty) actor.receive :: emptyBehaviorStack
else original.tail
}
从上面的代码可以发现:调用become(x)实际上是把x压进了一个堆栈里。如果像我们这样不断调用become转来转去的,在堆栈上留下旧的行为函数实例最终会造成StackOverFlowError。所以Akka提供了unbecome,这是个堆栈弹出函数,把上一个become压进的行为函数再弹出来,释放一个堆栈空间。所以我们应该用unbecome来解决堆栈溢出问题。但是,如果在多个receive函数之间转换来实现行为变化的话,就难以正确掌握堆栈的压进,弹出冲抵配对,并且无法避免所谓的意大利面代码造成的混乱逻辑。所以,become/unbecome最好使用在两个功能之间的转换。我们再设计一个例子来示范:
sealed trait DBOperations
case class DBWrite(sql: String) extends DBOperations
case class DBRead(sql: String) extends DBOperations sealed trait DBStates
case object Connected extends DBStates
case object Disconnected extends DBStates
DBoperations代表数据库读写操作。DBState代表数据库当前状态:连线Connected或断线Disconnected。只有数据库在Connected状态下才能进行数据库操作。顺理成章,我们需要两个receive函数:
import akka.actor._
sealed trait DBOperations
case class DBWrite(sql: String) extends DBOperations
case class DBRead(sql: String) extends DBOperations sealed trait DBStates
case object Connected extends DBStates
case object Disconnected extends DBStates object DBOActor {
def props = Props(new DBOActor)
} class DBOActor extends Actor with ActorLogging { override def receive: Receive = disconnected def disconnected: Receive = {
case Connected =>
log.info("Logon to DB.")
context.become(connected)
}
def connected: Receive = {
case Disconnected =>
log.info("Logoff from DB.")
context.unbecome()
case DBWrite(sql) =>
log.info(s"Writing to DB: $sql")
case DBRead(sql) =>
log.info(s"Reading from DB: $sql")
}
} object BecomeDB extends App {
val dbSystem = ActorSystem("dbSystem")
val dbActor = dbSystem.actorOf(DBOActor.props,"dbActor") dbActor ! Connected
dbActor ! DBWrite("Update table x")
dbActor ! DBRead("Select from table x")
dbActor ! Disconnected scala.io.StdIn.readLine()
dbSystem.terminate() }
运算结果显示如下:
[INFO] [// ::40.093] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Logon to DB.
[INFO] [// ::40.106] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Writing to DB: Update table x
[INFO] [// ::40.107] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Reading from DB: Select from table x
[INFO] [// ::40.107] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Logoff from DB.
以上是按正确顺序向dbActor发出数据库操作指令后产生的结果。但是,我们是在一个多线程消息驱动的环境里。发送给dbActor的消息收到时间无法预料。我们试着调换一下指令到达顺序:
dbActor ! DBWrite("Update table x")
dbActor ! Connected
dbActor ! DBRead("Select from table x")
dbActor ! Disconnected
运算结果:
[INFO] [// ::57.264] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Logon to DB.
[INFO] [// ::57.273] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Reading from DB: Select from table x
[INFO] [// ::57.273] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Logoff from DB.
漏掉了DBWrite操作。可以理解,所有connected状态之前的任何操作都不会真正生效。Akka提供了个Stash trait能把一个receive函数未处理的消息都存起来。然后用unstash()可以把存储的消息都转移到本Actor的邮箱里。我们可以用Stash来解决这个消息遗失问题:
def disconnected: Receive = {
case Connected =>
log.info("Logon to DB.")
context.become(connected)
unstashAll()
case _ => stash()
}
所有消息遗失都是在Disconnected状态内发生的。在disconnected里我们用stash把所有非Connected消息存起来,然后在转换成Connected状态时把这些消息转到信箱。再看看运算结果:
object BecomeDB extends App {
val dbSystem = ActorSystem("dbSystem")
val dbActor = dbSystem.actorOf(DBOActor.props,"dbActor") dbActor ! DBWrite("Update table x")
dbActor ! Connected
dbActor ! DBRead("Select from table x")
dbActor ! Disconnected scala.io.StdIn.readLine()
dbSystem.terminate() } [INFO] [// ::54.518] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Logon to DB.
[INFO] [// ::54.528] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Writing to DB: Update table x
[INFO] [// ::54.528] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Reading from DB: Select from table x
[INFO] [// ::54.528] [dbSystem-akka.actor.default-dispatcher-] [akka://dbSystem/user/dbActor] Logoff from DB.
显示结果正确。下面就是整个示范的源代码:
import akka.actor._
sealed trait DBOperations
case class DBWrite(sql: String) extends DBOperations
case class DBRead(sql: String) extends DBOperations sealed trait DBStates
case object Connected extends DBStates
case object Disconnected extends DBStates object DBOActor {
def props = Props(new DBOActor)
} class DBOActor extends Actor with ActorLogging with Stash { override def receive: Receive = disconnected def disconnected: Receive = {
case Connected =>
log.info("Logon to DB.")
context.become(connected)
unstashAll()
case _ => stash()
}
def connected: Receive = {
case Disconnected =>
log.info("Logoff from DB.")
context.unbecome()
case DBWrite(sql) =>
log.info(s"Writing to DB: $sql")
case DBRead(sql) =>
log.info(s"Reading from DB: $sql")
}
} object BecomeDB extends App {
val dbSystem = ActorSystem("dbSystem")
val dbActor = dbSystem.actorOf(DBOActor.props,"dbActor") dbActor ! DBWrite("Update table x")
dbActor ! Connected
dbActor ! DBRead("Select from table x")
dbActor ! Disconnected scala.io.StdIn.readLine()
dbSystem.terminate() }
Akka(6): become/unbecome:运算行为切换的更多相关文章
- Akka(0):聊聊对Akka的初步了解和想法
前一段时间一直沉浸在函数式编程模式里,主要目的之一是掌握一套安全可靠的并发程序编程方法(concurrent programming),最终通过开源项目FunDA实现了单机多核CPU上程序的并行运算. ...
- javascript运算符——位运算符
× 目录 [1]二进制 [2]非 [3]与[4]或[5]异或[6]左移[7]右移[8]>>>[9]应用 前面的话 位运算符是非常底层的运算,由于其很不直观,所以并不常用.但是,其速度 ...
- 2 数据分析之Numpy模块(1)
Numpy Numpy(Numerical Python的简称)是高性能科学计算和数据分析的基础包.它是我们课程所介绍的其他高级工具的构建基础. 其部分功能如下: ndarray, 一个具有复杂广播能 ...
- (54)Wangdao.com第七天_JavaScript 运算符
JavaScript 运算符,也叫操作符 对一个或者多个值进行运算,都是会返回结果的. 比如: typeof 返回一个值的类型,返回值类型是字符串. 隐式类型转换: 任意值 = 任意值 + &q ...
- (转)st(state-threads) coroutine调度
目录(?)[-] EPOLL和TIMEOUT TIME TIMEOUT Deviation st(state-threads) https://github.com/winlinvip/state ...
- CUDA 7.0 速查手册
Create by Jane/Santaizi 03:57:00 3/14/2016 All right reserved. 速查手册基于 CUDA 7.0 toolkit documentation ...
- 《JavaScript语言入门教程》记录整理:运算符、语法和标准库
目录 运算符 算数运算符 比较运算符 布尔运算符 二进制位运算符 void和逗号运算符 运算顺序 语法 数据类型的转换 错误处理机制 编程风格 console对象和控制台 标准库 Object对象 属 ...
- Akka(7): FSM:通过状态变化来转换运算行为
在上篇讨论里我们提到了become/unbecome.由于它们本质上是堆栈操作,所以只能在较少的状态切换下才能保证堆栈操作的协调及维持程序的清晰逻辑.对于比较复杂的程序流程,Akka提供了FSM:一种 ...
- Akka(8): 分布式运算:Remoting-远程查找式
Akka是一种消息驱动运算模式,它实现跨JVM程序运算的方式是通过能跨JVM的消息系统来调动分布在不同JVM上ActorSystem中的Actor进行运算,前题是Akka的地址系统可以支持跨JVM定位 ...
随机推荐
- C# treeview 使用笔记
treeView默认 展开 treeView1.ExpandAll(); treeview判断点击节点: private void treeView1_AfterSelect(object sende ...
- 读书笔记 effctive c++ Item 52 如果你实现了placement new,你也要实现placement delete
1. 调用普通版本的operator new抛出异常会发生什么? Placement new和placement delete不是C++动物园中最常遇到的猛兽,所以你不用担心你对它们不熟悉.当你像下面 ...
- 【代码学习】GD库中图片缩印
bool imagecopyresampled ( resource $dst_image, resource $src_image, int $dst_x, int $dst_y, int $src ...
- 子集构造法实现NFA的确定化
功能: 采用子集构造算法实现NFA的确定化 输入:读取NFA的文件(文件名test.txt), 文件格式: 第一列表示状态名,第二列和第三列分别表示输入字符a和b到达的状态 输出:确定化后的DFA( ...
- DirectFB 之 通过多Window实现多元素处理
图像 设计 采用多window的方式实现显示,因为每个window可以独立的属性,比如刷新频率,也是我们最关注的 示例 /*************************************** ...
- 使用Spigot搭建MineCraft服务器
MineCraft 这种游戏还是人多了好玩,以前的服务器放在同学的Windows电脑上,每次我们想玩的时候就让让去打开,但是总是有一些原因,想玩的时候服务器没开着,不想玩的时候服务器空开着费电.昨天一 ...
- 【算法系列学习】HDU 5527 Too Rich贪心
http://www.cnblogs.com/AOQNRMGYXLMV/p/4934747.html #include<iostream> #include<cstdio> # ...
- 【算法系列学习】SPFA邻接表最短路 [kuangbin带你飞]专题四 最短路练习 F - Wormholes
https://vjudge.net/contest/66569#problem/F 题意:判断图中是否存在负权回路 首先,介绍图的邻接表存储方式 数据结构:图的存储结构之邻接表 邻接表建图,类似于头 ...
- 音频特征提取——librosa工具包使用
作者:桂. 时间:2017-05-06 11:20:47 链接:http://www.cnblogs.com/xingshansi/p/6816308.html 前言 本文主要记录librosa工具 ...
- hdu4171 Paper Route 树的性质+DFS
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4171 题意: 有n+1个点,这n+1个点由n条边相连,且保证连通.然后给出各个点到出口的距离,要求从 ...