第30章.       Actor和并发(P607

程序有时候需要处理独立、并发、并行的情况。虽然Java对并发支持很好,但程序较复杂时,要正确处理并发也绝非易事。Scala继承了Java的所有并发支持,并提供了actor(动作执行器)。actor提供的并发模型使用更方便,避免了Java并发模型使用中的诸多难点。本章介绍Scala actor 库(scala.actors._)的基本用法,并把18章中的单线程例子转换为多线程版本。

(注:还有一种模型叫STMSoftware Transactional Memory,见http://multiverse.codehaus.org

30.1        并发是个麻烦事(P607

Java平台内建的线程模型基于“共享数据-锁”。每个object都和一个逻辑上的monitor相联系,该monitor控制多线程对共享数据的访问。使用Java线程模型处理一般是把需要多线程共享访问的数据或者代码部分用synchronized关键字进行标识。Java在运行时会采用锁机制保证在同一时刻只有一个线程能够运行被synchronized标识的那部分代码,从而控制多线程对共享数据的访问。

很不幸的是,要使用Java的“共享数据-锁”模型做出健壮的多线程应用非常困难,尤其当应用大小和复杂度增加的时候。问题在于,对于程序中的每个执行点,都要考虑哪些数据是可能被不止一个线程同时存取。对于每个方法调用,都要考虑要获得哪个线程锁并保证不引起死锁。线程锁的问题在编译时还无法定位,在运行时才会出现。

更糟糕的是,对多线程代码的测试并不可靠,因为线程是不可预测的,即便你自己测试了上千次都没有问题的程序,也有可能一装到客户的机器上就出错。共享数据和锁会让人觉得难以理喻。

    还有,也不是把所有代码都synchronized就能解决问题,这种做法过犹不及:因为synchronized减少了资源竞用冲突,但又会增加死锁的可能性。而正确使用锁的程序必须既要避免资源竞用冲突又得避免死锁,synchronized使用非常得当才能保证线程安全。

    Java 5 引入了更高层次上对并发编程进行抽象的java.util.concurrent库,相比原来确实能少出错,但concurrent库还是基于“共享数据-锁”模型,所以并不能解决根本的困难问题。

    Scalaactors库采用让程序员感觉更简单合理的“不共享数据,消息传递”模型。actors是设计并发软件的首选,能够避免由“共享数据-锁”模型引发的死锁和竞用冲突问题。

 

30.2        Actors和消息传递(P608

每个actor是类似线程,有接受消息的邮箱,需实现scala.actors.Actor接口(在Scala中是Trait)的act方法(类似于Java实现Thread或者Runnablerun方法)。如下例:

  import scala.actors._

  object SillyActor extends Actor {

    def act() {

      for (i <- 1 to 5) {

        println("I'm acting!")

        Thread.sleep(1000)

      }

    }

  }

actor通过SillyActor.start启动(类似JavaThread),打印5条信息后退出:

scala> SillyActor.start

res0: scala.actors.Actor = SillyActor$@a6023a

scala> I'm acting!

I'm acting!

I'm acting!

I'm acting!

I'm acting!

注意:该SillyActorScala命令行REPL程序(Read-Eval-Print Loop也就是我们说的交互解释执行)是两个不同线程,不同Actor之间是独立运行的,例如在下面建立另一个actor

  import scala.actors._

  object SeriousActor extends Actor {

    def act() {

      for (i <- 1 to 5) {

        println("To be or not to be.")

        Thread.sleep(1000)

      }

    }

  }

 

运行:

scala> SillyActor.start(); SeriousActor.start()

scala> I'm acting!

To be or not to be.

To be or not to be.

I'm acting!

To be or not to be.

I'm acting!

To be or not to be.

I'm acting!

To be or not to be.

I'm acting!

 

actor也可以通过scala.actors.Actor中的方法actor来创建:

  import scala.actors.Actor._

  val seriousActor2 = actor {

    for (i <- 1 to 5) {

      println("That is the question.")

      Thread.sleep(1000)

    }

  }

actor函数创建了一个actor,不需要start方法就马上开始运行。

    如上,可以创建多个独立运行的actor,那如何让他们协同工作呢?没有共享数据和锁,他们之间如何通信呢?答案就是actor之间的消息传递,用 ! 方法来传递消息,如:

scala> SillyActor ! "hi there"

当然由于SillyActor还没有处理消息的代码,上面的调用没有任何反应,"hi there"放在信箱中没被读取,下例的actor等待其信箱中的消息并在消息到达时唤醒receive

  import actors.Actor._

  val echoActor = actor {

    while (true) {

      receive {

        case msg =>

          println("received message: " + msg)

      }

    }

  }

actor发送消息不阻塞,当actor收到消息时也不中断,消息放到邮箱中,直到actor调用receive,如下:

scala> echoActor ! "hi there"

scala> received message: hi there

scala> echoActor ! 15

received message: 15

 

actor只处理和某个case匹配的消息,不匹配的消息不会放入信箱,receive方法会选取信箱中符合条件的第一条消息进行处理,如果信箱中没有符合条件的消息,receive会阻塞直至有符合条件的消息到来。

例如下面的actor只处理Int型的消息:

  import actors.Actor._

  val intActor = actor {

    receive {

      case x: Int => // I only want Ints

        println("Got an Int: " + x)

    }

  }

输入非Int型的消息,intActor会无声无息地忽略掉:

scala> intActor ! "hello"

scala> intActor ! Math.Pi

只有输入Int型的消息才会处理:

scala> intActor ! 12

Got an Int: 12