Scala使用Actor进行并发编程

来源:互联网 发布:lol遇到网络问题 编辑:程序博客网 时间:2024/05/22 03:38

原文:http://ghost-raidery.rhcloud.com/2013/11/28/scala%E4%BD%BF%E7%94%A8actor%E8%BF%9B%E8%A1%8C%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/

Akka 是一个用 Scala 编写的库,用于简化编写容错的、高可伸缩性的 Java 和 Scala 的 Actor 模型应用。 
Actor模型并非什么新鲜事物,它由Carl Hewitt于上世纪70年代早期提出,目的是为了解决分布式编程中一系列的编程问题。其特点如下:

  • 系统中的所有事物都可以扮演一个Actor
  • Actor之间完全独立
  • 在收到消息时Actor所采取的所有动作都是并行的,在一个方法中的动作没有明确的顺序
  • Actor由标识和当前行为描述
  • Actor可能被分成原始(primitive)和非原始(non primitive)类别
  • 非原始Actor有
    • 由一个邮件地址表示的标识
    • 当前行为由一组知识(acquaintances)(实例变量或本地状态)和定义Actor在收到消息时将采取的动作组成
  • 消息传递是非阻塞和异步的,其机制是邮件队列(mail-queue)
  • 所有消息发送都是并行的

使用Actor

Actor是Scala的并发模型。在2.10之后的版本中,使用http://akka.io/作为其推荐Actor实现。 
Actor是类似线程的实体,有一个邮箱。 
可以通过system.actorOf来创建,receive获取邮箱消息,!向邮箱发送消息。 这个例子是一个EchoServer,接受信息并打印。

  1. import akka.actor.{ Actor, ActorSystem, Props }
  2. val system = ActorSystem()
  3. class EchoServer extends Actor {
  4. def receive = {
  5. case msg: String => println("echo " + msg)
  6. }
  7. }
  8. val echoServer = system.actorOf(Props[EchoServer])
  9. echoServer ! "hi"
  10. system.shutdown

Actor更简化的用法

可以通过更简化的办法声明Actor。 导入akka.actor.ActorDSL中的actor函数。 这个函数可以接受一个Actor的构造器Act,启动并返回Actor。

  1. import akka.actor.ActorDSL._
  2. import akka.actor.ActorSystem
  3. implicit val system = ActorSystem()
  4. val echoServer = actor(new Act {
  5. become {
  6. case msg => println("echo " + msg)
  7. }
  8. })
  9. echoServer ! "hi"
  10. system.shutdown

Actor原理

Actor比线程轻量。在Scala中可以创建数以百万级的Actor。奥秘在于Actor可以复用线程。 
Actor和线程是不同的抽象,他们的对应关系是由Dispatcher决定的。 
这个例子创建4个Actor,每次调用的时候打印自身线程。 可以发现Actor和线程之间没有一对一的对应关系。 一个Actor可以使用多个线程,一个线程也会被多个Actor复用。

  1. import akka.actor.{ Actor, Props, ActorSystem }
  2. import akka.testkit.CallingThreadDispatcher
  3. implicit val system = ActorSystem()
  4. class EchoServer(name: String) extends Actor {
  5. def receive = {
  6. case msg => println("server" + name + " echo " + msg +
  7. " by " + Thread.currentThread().getName())
  8. }
  9. }
  10. val echoServers = (1 to 10).map(x =>
  11. system.actorOf(Props(new EchoServer(x.toString))
  12. .withDispatcher(CallingThreadDispatcher.Id)))
  13. (1 to 10).foreach(msg =>
  14. echoServers(scala.util.Random.nextInt(10)) ! msg.toString)
  15. system.shutdown

同步返回

Actor非常适合于较耗时的操作。比如获取网络资源。 
这个例子通过调用ask函数来获取一个Future。 在Actor内部通过 sender ! 传递结果。 Future像Option一样有很多高阶方法,可以使用foreach查看结果。

  1. import akka.actor.ActorDSL._
  2. import akka.pattern.ask
  3. implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
  4. implicit val system = akka.actor.ActorSystem()
  5. val versionUrl = "https://raw.github.com/scala/scala/master/starr.number"
  6. val fromURL = actor(new Act {
  7. become {
  8. case url: String => sender ! scala.io.Source.fromURL(url)
  9. .getLines().mkString("\n")
  10. }
  11. })
  12. val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000))
  13. version.foreach(println _)
  14. system.shutdown

异步返回

异步操作可以最大发挥效能。Scala的Futrue很强大,可以异步返回。 可以实现Futrue的onComplete方法。当Futrue结束的时候就会回调。 在调用ask的时候,可以设定超时。

  1. import akka.actor.ActorDSL._
  2. import akka.pattern.ask
  3. implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
  4. implicit val system = akka.actor.ActorSystem()
  5. val versionUrl = "https://raw.github.com/scala/scala/master/starr.number"
  6. val fromURL = actor(new Act {
  7. become {
  8. case url: String => sender ! scala.io.Source.fromURL(url)
  9. .getLines().mkString("\n")
  10. }
  11. })
  12. val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000))
  13. version onComplete {
  14. case msg => println(msg); system.shutdown
  15. }

并发集合

这个例子是访问若干URL,并记录时间。 如果能并发访问,就可以大幅提高性能。 尝试将urls.map修改为urls.par.map。这样每个map中的函数都可以并发执行。 当函数式和并发结合,就会这样让人兴奋。

  1. import scala.io.Codec
  2. import java.nio.charset.CodingErrorAction
  3. implicit val codec = Codec("UTF-8")
  4. codec.onMalformedInput(CodingErrorAction.REPLACE)
  5. codec.onUnmappableCharacter(CodingErrorAction.REPLACE)
  6. val urls = "http://scala-lang.org" :: "https://github.com/yankay/scala-tour" :: Nil
  7. def fromURL(url: String) = scala.io.Source.fromURL(url).getLines().mkString("\n")
  8. val s = System.currentTimeMillis()
  9. time(urls.map(fromURL(_)))
  10. println("time: " + (System.currentTimeMillis - s) + "ms")