akka actor

4 篇文章 0 订阅

Scala Actor 

简介

Actor提供了并发程序中与传统的基于锁的结构不同的另一种选择。通过尽可能避免锁和共享状态,Actor使得我们能够更加容易设计出正确、没有死锁或争用状态的程序。Scala类库提供了一个Actor模型的简单实现,除此之外还有其他更高级的Actor类库,比如Akka(http://akka.io)。

actor 并发模型中, actor 是独立的软件实体, 它们之间并不共享任何可变状态信息。 actor 之间无须共享信息, 通过交换消息的方式便可进行通
信。 通过消除同步访问那些共享可变状态, 编写健壮的并发应用程序变得非常简单。 尽管这些
actor 也许需要修改状态, 但是假如这些可变状态对
外不可访问, 并且
actor 框架确保 actor 相关代码调用是线程安全的, 开发者就无须再费力编写枯燥而又容易出错的同步原语 (synchronization  primitive) 了


创建和启动Actor

创建Actor

 

//继承Actor特质类
class HiActor extends Actor {
  //scala.actors.Actor
  //重写Actor特质类的抽象方法act,从而实现自己的业务逻辑
  override  def act(): Unit = {
    //类似于Java中的Runnable接口的run方法
    while (true)//消息循环
    //使用case模式匹配接收到的消息
      receive {
        case "Hi" => println("Hello")
      }
  }
}
val actor1 = new HiActor
actor1.start()

 

如果你需要临时创建actor而不是定义一个类。Actor伴生对象带有一个actor方法来创建和启动actor: 

valactor2 = actor {
    while (true) {
      receive {
        case "Hi" => println("Hello")
      }
    }
  }
}


 


case class Person(val name: String, val age: Int)
class HiActor extends Actor {
  override def act(): Unit = {
    while (true)   //消息循环
      receive {
        //case模式匹配接收到的消息
        case Person(name, age) => println("this is " + name + " :  age is " + age)
        case Person("Alice",20) => println("OK")
      }
    /*发送到actor的消息被存放在一个“邮箱”中
    receive方法从邮箱获取下一条消息并将它传递给它的参数,该参数是一个偏函数({ case xxxxxx})。
  */
  }
}

object exam1 {
  def main(args: Array[String]): Unit = {
    val actor1 = new HiActor
    actor1.start()
    //向actor1发送消息
    actor1 ! Person("Alice", 20)
    actor1 ! Person("Bob", 29)
    actor1 ! Person("Cindy", 16)
  }
}

 

·        消息投递的过程是异步的,它们会以什么样的顺序到达并不确定,所以在设计的时候要考虑到这一点,让程序不要依赖任何特定的消息投递顺序。

·        如果receive方法被调用时并没有消息,则该调用会阻塞,直到有消息抵达。

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

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

·        邮箱会串行化消息。Actor运行在单个线程中。它会先接收一条消息,然后接收下一条消息。不过在actor的代码中不用担心争用状况。


向其他Actor发送消息

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

 

Actor计算结果发送方向:

1.    可以是一些全局的actor。不过,当actor数量很多时,该方案伸缩性并不好。

2.    Actor可以构造成带有指向另一个或多个actor的引用。

3.    Actor可以接收带有指向另一个actor的引用消息。

4.    Actor可以返回消息给发送方。

 

 

消息通道

你可以通过共享消息通道来实现actor之间消息通信。

这样做的好处:

·        消息通道是类型安全的——你只能发送或接收特定的类型消息。

·        你不会不小心通过消息通道调用到某个actor方法。

 

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

 

要构造一个消息通道,你可以提供一个actor:

1.  val channel = new Channel[Int](actor1)  

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

 

代码示例:

import scala.actors.{Actor, Channel, OutputChannel}
import scala.actors.Actor._

case class Compute(input: Seq[Int], result: OutputChannel[Int])
class Computer extends Actor {
  override def act(): Unit = {
    while (true) {
      receive {
        case Compute(input, out) => {
          val answer = 88
          out ! answer
        }
      }
    }
  }
}
object exam1 {
  def main(args: Array[String]): Unit = {
    val actor1 = actor {
      val channel = new Channel[Int]
      val computeActor: Computer = new Computer
      val input: Seq[Int] = List(11, 22, 33)
      computeActor.start()
      computeActor ! Compute(input, channel)
      channel.receive {
        case x: Int => println("result is " + x)
        case _ => //xx
      }
    }
    actor1.start()
  }
}

 


同步消息

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

import scala.actors.Actor

case class Person(val name: String, val age: Int)
class HiActor extends Actor {
  override def act(): Unit = {
    while (true)
      receive {
        case Person(name, age) => {
          println("this is " + name + " :  age is " + age)
          sender ! "replied message..."
        }
      }
  }
}
object exam1 {
  def main(args: Array[String]): Unit = {
    val actor1 = new HiActor
    actor1.start()
    //向actor1发送消息并等待回复,用 !? 操作符即可。
    val reply = actor1 !? Person("Alice", 20)
    println(reply)   //"replied message..."
  }
}






这两种返回效果相同:

1.  sender ! "replied message..."  

2.  reply("replied message...")  

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

 

 

future

除了等待对方返回结果之外,你也可以选择接收一个future——这是一个将在结果可用时产出结果的对象。使用 !! 方法:

1.  val replyFuture = acount !! Deposite(1000)  

isSet方法会检查结果是否已经可用。要接收该结果,使用函数调用的表示方法:

1.  val reply = replyFuture()  

这个调用将会阻塞,直到回复被发送。

 

 

共享线程

某些程序包含的actor过多,以至于要为actor创建单独的线程开销会很大。此时需要考虑在同一个线程中运行多个actor。Actor有时大部分时间用户等待消息,这时actor所在的单独线程会堵塞,与其这样不如用一个线程来执行多个actor的消息处理函数。

 

在Scala中,react方法可以实现这样的功能。react方法接收一个偏函数,并将它添加到邮箱,然后退出。

 

react工作原理

当你调用一个actor的start时,start方法会以某种方式来确保最终会有某个线程来调用那个actor的act方法。如果act方法调用了react,则react方法会在actor的邮箱中查找传递给偏函数的能够处理的消息。(和receive方法一样,传递待处理消息给偏函数的isDefinedAt方法。)如果找到一个可以处理的消息,react 会安排一个在未来某个时间处理该消息的计划并抛出异常。如果它没有找到这样的消息,它会将actor置于“冷存储” 状态 ,在它通过邮箱收到更多消息时重新激活,并抛出异常。不论是哪种情况,react都会以这个异常的方式完成其执行,act 方法也随之结束  调用act的线程会捕获这个异常,忘掉这个actor , 并继续处理其他事务。这就是为什么你想要react在处理第一个消息之外做更多的事,你将需要在偏函数中再次调用act方法,或使用某种其他的手段来让react再次被调用。

 

较为复杂的代码示例

import java.net.InetAddress
import java.net.UnknownHostException
import actors._, Actor._
import scala.actors.Actor

case class Net(name: String, actor: Actor)

/**
  * scala Actor构建在java的线程基础之上的,
  * 为了避免频繁的线程创建、销毁和切换等,scala中提供了react方法
  * 方法执行完毕后,仍然被保留
  */
object NameResolver extends Actor {
  def act() {
    react {
      //模式匹配
      case Net(name, actor) =>
        /**向actor发送解析后的IP地址
          * 该例中,actor为本身,即向本身发送消息,存入邮箱
          */
        actor ! getIpAddress(name)
        //再次调用act方法,从本身邮箱中读取消息
        //如果消息为空,则等待
        act
      case "EXIT" =>
        println("Name resolver exiting.")
      //匹配邮箱中的单个信息,本例中会匹配邮箱中的IP地址信息
      case msg =>
        println("Unhandled message: " + msg)
        act
    }
  }

  def getIpAddress(name: String): Option[InetAddress] = {
    try {
      println("2: "+InetAddress.getByName(name))
      Some(InetAddress.getByName(name))
    } catch {
      case _: UnknownHostException => None
    }
  }
}


object ActorWithReAct extends App {
  //启动Actor
  NameResolver.start()
  //发消息
  NameResolver ! Net("www.baidu.com", self)

  //接收消息
  println("1: "+self.receive { case msg => msg })
}
//2: www.baidu.com/180.97.33.108
//1: Some(www.baidu.com/180.97.33.108)

Actor生命周期

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

 def act(): Unit ={  

    while (...){  

      receive{  

        //  

      }  

    }  

7.  }  

 actor在如下情形之一会终止执行:

·        Act方法返回

·        Act方法由于异常被终止

·        Actor调用exit方法

 exit方法是个受保护的方法。它只能被Actor子类中调用。例如:

  val actor2 = actor{  

    while(true){  

      receive{  

        case "Hi" => println("Hello")  

        case "Bye" => exit()  

      }  

    }  

  }  

其他方法不能调用exit()来终止一个actor。

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

 acotr: 抛出异常的actor。

·        message: Some(msg)  msg为该actor处理的最后一条消息;或者None,如果actor在没来得及处理任何消息之前就挂掉的话。

·        Sender:Some(channel)  channel代表最后一条消息的发送方的输出消息通道;或者None,如果actor在没来得及处理任何消息之前就挂掉。

·        Thread:  actor退出时所在的线程。

·        Cause: 相应的异常。

 

总结

1.    每个actor都要扩展Actor类并提供act方法。

2.    要往actor发送消息,可以用actor !Message。

3.    消息发送是异步的:“发完就忘”

4.    要接收消息,actor可以调用receive或react,通常是在循环中这样做。

5.    receive/react的参数是由case语句组成的代码块(是一个偏函数)

6.    不同actor之间不应该共享状态。总是使用消息来发送数据。

7.    不要直接调用actor方法。通过消息进行通信。

8.    避免同步消息——也就是说将发送消息和等待响应分开。

9.    不同actor可以通过react而不是receive来共享线程,前提是消息处理器的控制流转足够简单。

10. 让actor挂掉是可以的,前提是你有其他actor监控着actor生死。用链接来设置监控关系。

 

 

 

akka

Scala 已经将原先的 actor 库从类库中移除, 而将 Akka 视为处理基于 actor 并发模型的官方类库。 Akka 是一个独立项目。 Scala Akka 都由 Typesafe http://typesafe.com ) 开发并提供支持的, 同时 Akka 也提供了一套完整的 Java API

import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.Props
class HelloActor extends Actor {
  def receive = {
    case "hello" => println("您好!")
    case _ => println("您是?")
  }
}
object Main extends App {
  val system = ActorSystem("HelloSystem")
  // 缺省的Actor构造函数
  val helloActor = system.actorOf(Props[HelloActor], name = "helloactor") //返回类型是ActorRef
  helloActor ! "hello"
  helloActor ! "喂"
}

 

 

 

//#full-example
import akka.actor.{Actor, ActorRef, ActorSystem, Props}


//ActorRef是actor的引用
//#greeter-companion
//#greeter-messages
object Greeter {
  //#greeter-messages
  def props(message: String, printerActor: ActorRef): Props = Props(new Greeter(message, printerActor))

  //#greeter-messages
  final case class WhoToGreet(who: String)
  case object Greet

}

//#greeter-messages
//#greeter-companion

//#greeter-actor
class Greeter(message: String, printerActor: ActorRef) extends Actor {
  import Greeter._
  import Printer._
  var greeting = ""
  def receive = {
    case WhoToGreet(who) =>
      greeting = s"$message, $who"
    case Greet =>
      //#greeter-send-message
      printerActor ! Greeting(greeting)
    //#greeter-send-message
  }
}

//#greeter-actor

//#printer-companion
//#printer-messages
object Printer {
  //#printer-messages
  def props: Props = Props[Printer]
  //#printer-messages
  final case class Greeting(greeting: String)

}

//#printer-messages
//#printer-companion

//#printer-actor
class Printer extends Actor {

  import Printer._

  def receive = {
    case Greeting(greeting) =>
      println(s"Greeting received (from ${sender}): $greeting")
  }
}

//#printer-actor

//#main-class
object AkkaQuickstart extends App {

  import Greeter._

  // Create the 'helloAkka' actor system
  val system: ActorSystem = ActorSystem("helloAkka") //是actor创建工厂

  //#create-actors
  // Create the printer actor
  val printer: ActorRef = system.actorOf(Printer.props, "printerActor")

  // Create the 'greeter' actors
  val howdyGreeter: ActorRef =
    system.actorOf(Greeter.props("Howdy", printer), "howdyGreeter"//第一个参数是Props,用于创建actor
  val helloGreeter: ActorRef =
    system.actorOf(Greeter.props("Hello", printer), "helloGreeter")
  val goodDayGreeter: ActorRef =
    system.actorOf(Greeter.props("Good day", printer), "goodDayGreeter")
  //#create-actors

  //#main-send-messages
  howdyGreeter ! WhoToGreet("Akka")
  howdyGreeter ! Greet

  howdyGreeter ! WhoToGreet("Lightbend")
  howdyGreeter ! Greet

  helloGreeter ! WhoToGreet("Scala")
  helloGreeter ! Greet

  goodDayGreeter ! WhoToGreet("Play")
  goodDayGreeter ! Greet
  //#main-send-messages
}

//#main-class
//#full-example

  

 

 

import akka.actor._
case object PingMessage
case object PongMessage
case object StartMessage
case object StopMessage
class Ping(pong: ActorRef) extends Actor {
  var count = 0
  def incrementAndPrint { count += 1; println("ping") }
  def receive = {
    case StartMessage =>
      incrementAndPrint
      pong ! PingMessage
    case PongMessage =>
      if (count > 9) {
        sender ! StopMessage
        println("ping stopped")
        context.stop(self)
      } else {
        incrementAndPrint
        sender ! PingMessage
      }
  }
}
class Pong extends Actor {
  def receive = {
    case PingMessage =>
      println(" pong")
      sender ! PongMessage
    case StopMessage =>
      println("pong stopped")
      context.stop(self)
      context.system.shutdown()
  }
}
object PingPongTest extends App {
  val system = ActorSystem("PingPongSystem")
  val pong = system.actorOf(Props[Pong], name = "pong")
  val ping = system.actorOf(Props(new Ping(pong)), name = "ping")
  // start them going
  ping ! StartMessage
}

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值