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定位 ...
随机推荐
- linux C/C++ 日志打印函数
//宏定义日志文件名 #define PROCESSNAME "log_filename" //当日志文件大于5M时,会删除该文件,该接口使用方法 参照printfvoid Wr ...
- Java--定时器问题
定时器问题 定时器属于基本的基础组件,不管是用户空间的程序开发,还是内核空间的程序开发,很多时候都需要有定时器作为基础组件的支持.一个定时器的实现需要具备以下四种基本行为:添加定时器.取消定时器.定时 ...
- python数据处理——numpy_2
上一次的学习了numpy的一些基础操作,今天接着学习numpy的高级索引.轴对换数值转置以及作图. #花式索引 import numpy as np ''' t = np.empty((8,4)) # ...
- 杜教筛 && bzoj3944 Sum
Description Input 一共T+1行 第1行为数据组数T(T<=10) 第2~T+1行每行一个非负整数N,代表一组询问 Output 一共T行,每行两个用空格分隔的数ans1,ans ...
- 针对Mac的DuckHunter攻击演示
0x00 HID 攻击 HID是Human Interface Device的缩写,也就是人机交互设备,说通俗一点,HID设备一般指的是键盘.鼠标等等这一类用于为计算机提供数据输入的设备. DuckH ...
- [系统启动]Printk与sched_clock_init的一点分析
作者:Younger Liu, 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可. 在分析Linu内核启动的过程中,发现一段"不平常" ...
- 使用PhpIniDir加载php配置文件出错,不能解析。
我也是在apache服务器上配置php,在加载了php的配置文件之后,就出问题了. PHPIniDir "H:/php54/"增加了类似这句话之后,服务器就启动不了了,或者.php ...
- Xcode8插件安装
一.创建一个自定义证书并且为Xcode重新签名1.打开钥匙串 2.创建自定义签名证书 3.重新签名Xcode(速度比较慢,大概要等1分钟) $ sudo codesign -f -s XcodeSig ...
- 关于[JAVA] Apache FTPClient.listFiles()操作“卡死”问题的分析和解决
项目中使用commons-net-3.0.1.jar实现FTP文件的下载,程序运行到 FTPClient.listFiles()或者FTPClient.retrieveFile()方法时,就停止在那里 ...
- 跨域问题解决方案(HttpClient安全跨域 & jsonp跨域)
1 错误场景 今天要把项目部署到外网的时候,出现了这样的问题, 我把两个项目放到自己本机的tomcat下, 进行代码调试, 运行 都没有问题的, 一旦把我需要调用接口的项目B放到其他的服务器上, 就会 ...