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定位 ...
随机推荐
- Neural Networks and Deep Learning(神经网络与深度学习) - 学习笔记
catalogue . 引言 . 感知器及激活函数 . 代价函数(loss function) . 用梯度下降法来学习-Learning with gradient descent . 用反向传播调整 ...
- stl_container容器和std_algorithm算法相同的函数
八.算法和容器中存在的功能相同的函数: 8.1.array: 8.1.1.fill. 1.在array中:void fill (const value_type& val); 2.在algor ...
- vector介绍
vector(向量,也可称为容器): C++中的一种数据结构,确切的说是一个类.它相当于一个动态的数组,当程序员无法知道自己需要的数组的规模多大时,用其来解决问题可以达到最大节约空间的目的. 1.1 ...
- 【原创】Android 5.0 BLE低功耗蓝牙从设备应用
如果各位觉得有用,转载+个出处. 现如今安卓的低功耗蓝牙应用十分普遍了,智能手环.手表遍地都是,基本都是利用BLE通信来交互数据.BLE基本在安卓.IOS两大终端设备上都有很好支持,所以有很好发展前景 ...
- 蓝桥杯-猜字母-java
/* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...
- 【翻译】FreeMarker——入门
原文传送门 1. Template + data-model = output data-model是一个树状模型,通常是一个java对象. 2.data-model 入门 hashes(散列):目录 ...
- 当谈 SQL 优化时谈些什么?
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 作者:孙银行 背景 Mysql数据库作为数据持久化的存储系统,在实际业务中应用广泛.在应用也经常会因为SQL遇 ...
- poj2398计算几何叉积
Mom and dad have a problem: their child, Reza, never puts his toys away when he is finished playing ...
- 用PHP删除ftp下载导致的文件空行
使用FTP上传下载文件,如果没有设置传输方式为二进制,可能会导致文件出现空行的情况,例如: 使用PHP对文件的空行进行删除,使用正则: <?php $file = './abc.inc.php' ...
- linux下vim编辑器使用
VIM - Vi IMproved: vim是vi编辑器的升级版,是linux下标准的编辑器,具有程序编写能力,可以根据字体颜色辨别语法的正确性,方便程序的设计. 使用: # vim [OPTION] ...