一.本章要点

  • 每个actor都要扩展Actor类并提供act方法
  • 要往actor发送消息,可以用actor!message
  • 消息发送是异步的:”发完就忘“
  • 要接受消息,actor可以调用receive或react,通常是在循环中这样做
  • receive/react的参数是有case语句组成的代码块(偏函数)
  • 不同actor之间不应该共享状态。总是使用消息来发送数据
  • 不要直接调用actor的方法,通过消息进行通信
  • 避免同步消息——将发送消息和等待响应分开
  • 不同actor可以通过react而不是receive来共享线程,前提是消息处理器的控制流转足够简单
  • 让actor挂掉是OK的,前提是你有其他actor监控着actor的生死。用链接来设置监控关系

二.创建和启动Actor

  actor是扩展自Antor特质的类。该特质有一个抽象方法act。可以重写这个方法指定avtor的行为。act方法带有一个消息循环,例:

import scala.actors.Actor

class HiActor extends Actor{
def act(){while(true){
receive{
case "Hi"=>println("Hello")}
}}
}
//调用start方法执行
val actor1=new HiActor
actor1.start()

  act方法与Java中Runable接口中的run方法很相似,正如不同线程的run方法那样,不同actor的act方法也是并行运行的,对响应信息做了优化。  

  临时创建actor,利用Actor伴生对象的actor方法创建和启动actor:

import scala.actors.Actor._
val actor=actor{
while(true){
receive{case "Hi"=>println("Hello")
}
}
}

三.发送消息

  actor是一个处理异步消息的对象。

  消息可以是任何对象。

  使用!操作符发送小心,消息被发送当前线程继续执行——”发完就忘“(可以等待一个回复),一个好的做法是使用样例类作为消息,让actor使用模式匹配来处理消息。

四.接受消息

  发送到actor的消息被存放在一个”邮箱“中,receive方法从邮箱获取下一条信息并将它传递给它的参数,该参数是一个偏函数。例:

receive {
case Deposit(amount)=>....
case Withdraw(amount)=>...
}
//receive的参数是{case Deposit(amount)=>....
//case Withdraw(amount)=>...
//}

  该代码块被转换成一个类型为PartialFunction[Any,T]的对象,其中T是case语句=>操作符右边的表达式的计算结果的类型。这是个偏函数,因为它对那些能够匹配其中一个case语句的参数有定义;

  注:消息传递的过程是异步的。(到达顺序不确定,设计时应让应用程序不要依赖任何特定的消息投递顺序);

    receive方法被调用时并没有消息,则该调用阻塞,直到有消息抵达,如果邮箱中没有任何消息可以被偏函数处理,则对receive方法的调用也会阻塞,直到一个可以匹配的消息抵达;  

    邮箱有可能被那些不与任何case语句匹配的消息占满。添加一个case _语句来处理任意的消息;

    邮箱会串行化信息:actor运行在单个线程中(不用担心争用状况);

    actor可以很安全的修改自己的数据(但如果修改了在不同actor之间共享的数据,那么争用情况就有可能出现),因此不要在不同的actor中使用共享对象(除非是线程安全的)    

五.向其他Actor发送消息

  当运算被拆分到不同的actor来并行处理问题的各个部分时,这些处理结果需要被收集到一起。actor可以将结果存入到一个线程安全的数据结构当中(如并发的哈希映射),但actor模型并不鼓励使用共享数据。因此当actor计算结果后,应该向另一个actor发送消息。

  actor是如何知道应该往哪里发送计算结果:

    1.可以有一些全局的actor(当actor数量很多时,这个方案的伸缩性不好);

    2.actor可以构造成带有指向一个或多个actor的引用(当antor持有另一个actor的引用时,它只应该使用这个引用来发送消息,而不是调用方法。这样做违背了actor精神,同时还可能引发争用状况——这正是actor设计出来要避免的问题);

    3.actor可以接收带有指向另一个actor的引用消息。在请求中提供一个actor引用是很常见的做法,例:actor ! Compute(date,continuation)

    4.actor可以返回消息给发送方,receive方法会把sender字段设为当前消息的发送方

六.消息通道

  除了在应用程序中对actor共享引用的做法,还可以共享消息通道给它们。

  优点:1.消息通道是类型安全的——你只能发送或接受某个特定类型的消息;2.不会不小心通过消息通道调用到某个actor的方法。

  消息通道可以是一个OutputChannel(带有!方法),也可以是一个InputChannel(带有receive或react方法)。Channel类同时扩展OutputChannel和InputChannel特质。

  构造一个消息通道(需要提供一个actor):val channel=new Channel[Int][someActor]

  如果不提供构造参数,那么消息通道就会绑定到当前执行这个actor上:

case class Compute(input:Seq[Int],result:OutputChannel[Int])
class Computer extends Actor{
public void act(){
while(true){
receive{
case Compute(input,output)=>{val anwser=...;out!anwser}
}}
}
} actor {
val channel=new Channel[Int]
val computeActor:Computer=...
val input:Seq[Double]=...
computeActor!Compute(input,channel)
channel.receive{
case x=>...//已知x是一个Int
}
}

  注意:这里调用的是channeldereceive而不是actor自己,如果想要通过actor来接收相应,可以匹配一个!样例类的实例

七.同步消息和Future

  actor可以发送一个消息并等待回复,用!?操作符即可,例:

val reply=account!?Deposit(1000)
reply match{
case Balance(bal)=>println("Current Balance:"+bal)
}

  要让它工作,接收方必须返回一个消息给发送方:  

receive {
case Deposit(amount)=>{
//除了用sender!Balance(balance),也可以写reply(Balance(balance))
balance+=amount;sender!Balance(balance)
...
}
}

  注:同步消息容易引发死锁,通常而言,最好避免在actor的act方法里执行阻塞调用。

  使用receiveWithin方法指定需要等待多少毫秒的时间(不想对一个回复永久等待),如果在指定时间没有收到消息,就会收到一个Actor.TIMEOUT对象,例:

actor {
worker !Task(data,self)
receiveWithin(secends*1000){
case Result(data)=>,,,
case TIMEOUT=>lof(...)
}
}

  注:react方法也有一个带时间的版本,称做reactWithin。

  除了等待对方返回结果之外,也可以选择接收一个future——这是一个将在结果可用时产出结果的对象,使用!!方法得到:val replyFuture=ammount!!Deposit(1000),isSet方法会检查结果是否可用,要接受结果,使用函数调用的表示法:val replyFuture=replyFuture(),这个调用将会阻塞,直到消息被发送。注:如果立即想从future接受返回的结果,那么你并没有享受到任何优于同步方法调用的好处,不过,actor可以将future放到一边,稍后再做处理,或将它交给其他actor。

八.共享线程

  上述在Scala中,react方法接受一个偏函数,并将它添加到邮箱,然后退出,例:

react {//偏函数f1
case Withdraw(amount)=>{
react {//偏函数f2
case Confirm()=>{
println("Confirming"+amount)
}
}
}
}
//第一个react的调用将f1与actor的邮箱关联起来,然后退出。当Withdraw消息抵达时,f1被调用,偏函数f1也调用react,这次调用把f2与actor的邮箱关联起来,然后退出,当Confirm消息抵达时,f2被调用

  注:第二个react可能在一个单独的函数中,因此,react需要抛出异常,从而得以退出。

    关联的偏函数不会返回一个值——它执行了某些工作,然后执行下一个react,就造成退出了。退出意味着返回到协调的actor的方法中,这样的一个函数的返回类型为Nothing,该类型用来表示正常退出。

//由于react会退出,因此不能简单的把它放在while循环中,如:

def act(){
while(true){
react{//偏函数
case Withdraw(amount)=>println("Withdrawing"+amount)
}
}
}
//当act被调用时,对react的调用将f1与邮箱关联起来,然后退出,当f1被调用时,将会处理这条消息
{case Withdraw(amount)=>println("Withawing "+amount)}
//解决方法一:在消息处理器中再次调用act方法(无穷递归替换掉无穷循环,这个无穷递归不会占用很大的栈空间,每次对react的调用都会抛出异常从而清栈)
def act(){
react{//偏函数
case Wirhdraw(amount)=>{
println("Withawing"+amount)
act()
}
}
} //让每个消息处理器自己负责保持循环继续进行下去看上去并不很公平,于是有一些”控制流转组合子“可以自动产出这些循环
//loop组合子可以制作一个无穷循环
def act(){
loop{
react{
case Withdraw(amount)=>process(amount)}
}}
//如果需要一个循环条件,可以用loopWhile
loopWhile(count>max){
react{
...}
} //eventloop方法可以制作一个无穷循环套react的简化版,不过提前是偏函数不会再次调用react
def act(){
eventloop{
case Withdraw(amount)=>println("Withdrawing"+amount)
}
}

九.Actor的生命周期

  actor的act方法在actor的start方法被调用时开始执行。actor接下来做的事情是进入某个循环,例:

def act(){
while(...){
receive{
...}
}
}
//actor在如下情形之一会终止执行:

  1.act方法返回
  2.act方法由于异常被终止
  3.actor调用exit方法
×/ 

  注意:exit方法是个受保护的方法。它只能被Actor的子类中调用(其他方法不能调用exit方法来终止一个actor)。例:

//其他方法也不能调用exit()来终止
val actor1=actor {
while(true){
receive{
case "Hi"=>println("Hello")
case "Bye"=>exit()
}
}
}

  还有一个exit方法的重载版本可以接受一个参数描述退出原因。不带参数调用exit相当于exit('normal)。

  当actor因一个异常终止时,退出原因就是UncaughtException样例类的一个实例,该样例类有如下属性:

      • actor:抛出异常的actor
      • message:Some(msg),其中msg是该actor处理的最后一条消息或者None,如果actor在没来得及处理任何消息之前就挂掉的话;
      • sender:Some(channel),其中channel是代表最后一条消息的发送方的输出消息通道或者None,如果actor在没来得及处理任何消息之前就挂掉的话;
      • thread:actor退出时所在的线程;
      • case:相应的异常

十.将多个Actor链接在一起

  将两个actor链接在一起,则另一个都会在另一个终止执行的时候得到通知(调用link方法即可):

def act(){
link(master)
}

  链表是双向的(如监管actor在数个工作actor中分发工作任务,那么当某个工作actor挂掉的时候,监管actor应该知道,以便相应的工作任务可以重新指派,反过来,如果监控actor挂掉了,工作actor也应该知道,以便可以停止工作)

  注:尽管链接是双向的,但link方法并不是对称的,不能呢将link(worker)替换成worker.link(self),该方法必须由请求链接的actor调用。

  默认情况下,只要当前actor链接到的actor中有一个非’normal原因退出,当前actor就会终止(在这种情况下,退出原因和链接到那个actor的退出原因相同)。

  actor可以改变这个行为,做法是设置trapExit为true,修改后mactor会接受到一个类型为Exit的消息,该消息包含了那个正在终止的actor和退出的原因。例:

override def act(){
trapExit=true
link(wprker)
while(...){
receive{
...
case Exit(linked,UncaughtException(_,_,_,case))=>...
case Exit(linked,reason)=>...
}
}
}

  在操作actor时,允许它们挂掉是正常的。只需要把每个actor链接到一个”监管“actor,由它来处理失败的actor,例把工作重新分配或者重新启动。

  

  

十一.Actor的设计

  使用和操作actor注意:

十二.练习

Scala学习二十——Actor的更多相关文章

  1. Scala学习二十二——定界延续

    一.本章要点 延续让你可以回到程序执行当中之前的某个点; 可以在shift块中捕获延续 延续函数一直延展到包含它的reset块的尾部 延续所谓的”余下的运算“,从包含shift的表达式开始,到包含它的 ...

  2. Java开发学习(二十二)----Spring事务属性、事务传播行为

    一.事务配置 上面这些属性都可以在@Transactional注解的参数上进行设置. readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true. timeout ...

  3. Java开发学习(二十四)----SpringMVC设置请求映射路径

    一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...

  4. Java开发学习(二十五)----使用PostMan完成不同类型参数传递

    一.请求参数 请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求,接收到请求后,如何接收页面传递的参数? 关于请求参数的传递与接收是和请求方 ...

  5. Java开发学习(二十六)----SpringMVC返回响应结果

    SpringMVC接收到请求和数据后,进行了一些处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户. 比如:根据用户ID查 ...

  6. Java开发学习(二十八)----拦截器(Interceptor)详细解析

    一.拦截器概念 讲解拦截器的概念之前,我们先看一张图: (1)浏览器发送一个请求会先到Tomcat的web服务器 (2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源 (3)如 ...

  7. scala 学习笔记十二 继承

    1.介绍 继承是面向对象的概念,用于代码的可重用性.可以通过使用extends关键字来实现继承. 为了实现继承,一个类必须扩展到其他类,被扩展类称为超类或父类.扩展的类称为派生类或子类. Scala支 ...

  8. Scala学习(二)--- 控制结构和函数

    控制结构和函数 摘要: 本篇主要学习在Scala中使用条件表达式.循环和函数,你会看到Scala和其他编程语言之间一个根本性的差异.在Java或C++中,我们把表达式(比如3+4)和语句(比如if语句 ...

  9. Scala学习二十一——隐式转换和隐式参数

    一.本章要点 隐式转换用于类型之间的转换 必须引入隐式转换,并确保它们可以以单个标识符的形式出现在当前作用域 隐式参数列表会要求指定类型的对象.它们可以从当前作用域中以单个标识符定义的隐式对象的获取, ...

随机推荐

  1. 笔记七(编写第一个UEFI程序)

    搭建好uefi开发环境之后,在MyWorkspace文件夹中建立一个文件夹ExamplePkg; ,然后在ExamplePkg文件夹中创建HelloWorld文件夹,Include文件夹,Exampl ...

  2. 快速上手系列-C语言之指针篇(一)

    快速上手系列-C语言之指针篇(一) 浊酒敬风尘 发布时间:18-06-2108:29 指针的灵活运用使得c语言更加强大,指针是C语言中十分重要的部分,可以说指针是C语言的灵魂.当然指针不是万能的,但没 ...

  3. Sublime Text 全局搜索Ctrl+Shift+F快捷键不能用

      Sublime Text 全局搜索Ctrl+Shift+F快捷键不能用   和微软输入法的简繁体切换冲突了,关闭输入法的简繁体切换快捷键就好了! 文章来源:刘俊涛的博客 欢迎关注,有问题一起学习欢 ...

  4. linux下如何交叉编译util-linux?

    1. 获取源码 wget https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.34/util-linux-2.34.tar.xz ...

  5. ajax传参数json对象到后台获取

    类型1 var version = $("#version").val(); var ids[i] = ("127.0.0.1","192.168.1 ...

  6. 【MYSQL】存储过程示例

    GROUPEMP_EXISTS: ), ), )) LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT ' ...

  7. PAT 甲级 1028 List Sorting (25 分)(排序,简单题)

    1028 List Sorting (25 分)   Excel can sort records according to any column. Now you are supposed to i ...

  8. 多个celery如何使用同一个redis做为broker?

    曾经有个哥们提了一个这方面的requests,但是最终没有合入的celery中去,所以目前celery没有这个功能,祥见: https://github.com/celery/kombu/pull/9 ...

  9. 编写expect程序报extra characters after close-brace错误或extra characters after close-quote,解决

    expect程序报extra characters after close-brace或extra characters after close-quote 可能原因 流程控制语句中的"{& ...

  10. (4)Linux命令分类汇总(13~16)

    Linux命令分类汇总(13~16) (十三)系统管理与性能监视命令(9个) 79       chkconfig 管理Linux系统开机启动项. 80       vmstat 虚拟内存统计. 81 ...