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定位 ...
随机推荐
- AngularJS的过滤器$filter
过滤器(filter)主要用于数据的格式上,通过某个规则,把值处理后返回结果.例如获得数据集,可排序后再返回. ng内置的共有九种过滤器: currency 货币 使用currency可以将数字格式化 ...
- html基础认识,高手别看
HTML5是一种用于在万维网上构建和呈现内容的符号言语.它是HTML规范的第五和当时版别.它是由万维网联盟(W3C)在十月发布的2014 [ 2 ] [ 4 ]和最新的多媒体支持进步言语,一起坚持它简 ...
- laravel5 事务回滚
方法一 //不需要引入,直接开干 public function Transaction(){ DB::beginTransaction(); //开启事务 $sql1 = DB::table('de ...
- 573. Squirrel Simulation
Problem statement: There's a tree, a squirrel, and several nuts. Positions are represented by the ce ...
- [ABP实战开源项目]--YoYoCms目录
ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WE ...
- TPshop分销功能的使用与表设计
首先来段科普,摘自百度百科: 在西方经济学中,分销 的含义是建立销售渠道的意思,根据著名的营销大师菲利普·科特勒的定义,分销渠道(Distribution Channel)又或者叫营销渠道(Marke ...
- 文件查找---find
文件查找(find) 1 find 简单的说,就是实时查找指定的内容或条件.特点:最新.最快.最准确. 用法:find [查找路径] [查找条件] [找到后进行的动作] eg. find /e ...
- jQuery选择器---层次选择器总结
今天要分享的是jQuery层次选择器,层次选择器的分类如图: 接下来就开始了 要不先养养眼精神一下: 开始1.祖先选择器: 案例: <form> <label>Name:< ...
- 【SoDiaoEditor电子病历编辑器】阶段性更新啦
转眼距离上一次v2正式发布已经过去一个半月了.github期间不定期push了二十几次,同时感谢分布在广州.福建.上海.北京的一众小伙伴,正是你们给出的建议,才让SoDiaoEditor不断完善. 我 ...
- Android分享功能实现
通过系统分享组件实现分享功能 Intent.createChooser() 方法用来弹出系统分享列表. createChooser(Intent target, CharSequence title, ...