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定位 ...
随机推荐
- [JavaWeb]SpringSecurity-OAuth2.0 统一认证、资源分离的配置,用于分布式架构、模块化开发的认证体系
前言 关于 OAuth2.0的认证体系,翻阅了好多资料,RCF 文档太多,看了一半就看不下去了,毕竟全英文的文档看起来,是有一点让我烦躁,但也对 OAuth2.0的认证流程有了一个基本的概念,之前用 ...
- MySQL 事务与锁机制
下表展示了本人安装的MariaDB(10.1.19,MySQL的分支)所支持的所有存储引擎概况,其中支持事务的有InnoDB.SEQUENCE,另外InnoDB还支持XA事务,MyISAM不支持事务. ...
- C#事物
执行ADO.NET事务包含四个步骤,分别为: ①调用SqlConnection对象的BeginTransaction()方法,(只调用这个方法前,要打开数据库连接,否则将会出现异常) 创建一个SqlT ...
- 腾讯云无法绑定公网IP问题解释与解决方案。
http://blog.csdn.net/chenggong2dm/article/details/51475222 解释:公网IP并不直接配置在服务器上,而是在服务器外部的路由上,通过某种映射连接. ...
- 配置web.xml和glassfish容器实现javaEE表单验证
web.xml配置: <!-- 声明用于安全约束的角色 --> <security-role> <role-name>ReimUser</role-name& ...
- ABP文档 - 对象与对象之间的映射
文档目录 本节内容: 简介 IObjectMapper 接口 集成 AutoMapper 安装 创建映射 自动映射的特性 自定义映射 扩展方法 MapTo 单元测试 预定义的映射 Localizabl ...
- js获取一组不重复的随机数的方法
一.基本思路: 建立一个数组存放所有可以取到的值,每次从该数组中随机取走一个,放到新的数组中,直到完成. 二.实现方法 1.方法一: (1)创建一个数组arr,数组元素为所有可能出现元素的集合: (2 ...
- LeetCode4. Median of Two Sorted Arrays---vector实现O(log(m+n)--- findkth
这道题目和PAT上的1029是同一题.但是PAT1029用O(m+n)的时间复杂度(题解)就可以,这道题要求是O(log(m+n)). 这道题花费了我一个工作日的时间来思考.因为是log因而一直思考如 ...
- java之反射
初学反射,也是第二次写博客了把,就简单记录一下. Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对 ...
- XSHELL工具上传文件到Linux以及下载文件到本地(Windows)
Xshell很好用,然后有时候想在windows和linux上传或下载某个文件,其实有个很简单的方法就是rz,sz.首先你的Linux上需要安装安装lrzsz工具包,(如果没有安装请执行以下命令,安装 ...