使用actor并发
scala的actor提供了一种基于事件的轻量级线程。只要使用scala.actors.Actor伴生对象的actor()方法,就可以创建一个actor。它接受一个函数值/闭包做参数,一创建好就开始运行。用!()方法给actor非诉讼消息,用receive()方法从actor接受消息。receive()可以闭包为参数,通常用模式匹配处理接收到的消息。
def sumOfFactorsInRange(lower: Int,upper:Int, number:Int) = {
var sum = 0
for (i <- lower to upper) {
if (number % i == 0) sum += i
}
sum
}
def isPerfectConcurrent(candicate: Int) = {
val range = 10000
val partitions = (candicate.toDouble / range).ceil.toInt
val caller = self//主线程
for (i <- 0 until partitions) {
val lower = i * range + 1
val upper = candicate.min(i + 1) * range
actor {
//发送消息
caller ! sumOfFactorsInRange(lower, upper, candicate)
}
}
val sum = (0 /: (0 until partitions)) {(partialsum ,i) =>
receive {
//接受,并采用模式匹配
case sumInRange: Int => partialsum + sumInRange
}
}
2 * candicate == sum
}
消息传递
每个actor都有自己的消息队列,它从InputChannel[Any]接收输入,通过OutputChannel[Any]发送输出。这些消息顺序的存在队列中。发送消息时,actor不会阻塞,不过调用receive方法时,actor就会阻塞在那里。
package com.fanshadoop.thread
import scala.actors.Actor._
object MessagePass {
def main(args:Array[String]) = {
var starttime: Long = 0
val caller = self
val engossedActor = actor {
println("number of message recievieds so far" + mailboxSize)
caller ! "send"
Thread.sleep(3000)
println("number of message recievies while was busy" + mailboxSize)
receive {//接收消息,actor调用此方法之前一直阻塞
case msg =>
val time = System.currentTimeMillis() - starttime
println("received message " + msg + " after"+ time)
}
//向调用者发送消息
caller ! "mail loop I received"
}
receive {
case _ => println("main loop received message" + mailboxSize)
}
println("send message")
starttime = System.currentTimeMillis()
engossedActor ! "hello buddy"
val endtime = System.currentTimeMillis() - starttime
printf("take less then %dms to send message\n",endtime)
Thread.sleep(3000)
receive {
case msg: String => println("main loop received message" + mailboxSize)//一直阻塞,直到等到消息
}
}
}
Actor类
如果想在actor启动时显式控制,希望在actor里存入更多信息,可以创建一个对象,混入actor trait。
package com.fanshadoop.thread
import scala.actors.Actor
import Actor._
object AnswerService {
class Service(val forks: String*) extends Actor {
def act() {
while (true) {
receive {
case (caller: Actor, name : String, message:String) =>
caller ! (
if (forks.contains(name)) String.format("Hey %s got message %s", name, message)
else String.format("Hey there is not any message with name %s", name)
)
case "ping" => println("ping");
case "exit" => println("exit");
System.exit(0)
}
}
}
}
def main(args: Array[String]): Unit = {
val serive = new Service("sara", "kara", "john")
val caller = self
serive ! (self, "sara", "hello")
serive ! "ping"
serive.start
serive ! "ping"
serive ! (self, "kara", "hello john")
serive ! "exit"
for (i <- 0 to 3) {
receive {
case msg : String => println("get response" + msg)
case _ => println("nothing")
}
}
}
}
在实现类里,必须实现act方法(在Actor trait中act方法时抽象的)。actor没有启动,发送的消息会进入队列,等待后续处理。
actor方法
与actor交互的顺序是没有任何保证的,actor接收到的消息可以进行处理,只要准备好就可以应答。actor对消息的接受和处理没有预先强加的顺序。
package com.fanshadoop.thread
import scala.actors.Actor
import Actor._
object PrimeTeller {
def isPrimer(number: Int) = {
println("going to find number is primer")
var result = true
if (number == 2 || number == 3) result = true
for (i <- 2 to Math.sqrt(number.toDouble).floor.toInt; if result) {
if (number % i == 0) result = false
}
println("going to find number " + number + " is primer " + result)
result
}
def main(args: Array[String]): Unit = {
val teller = actor {
var continue = true
while (continue) {
receive {
case (caller: Actor, number: Int) => caller ! (number, isPrimer(number))
case "quit" => continue = false
}
}
}
teller ! (self, 5)
teller ! (self, 19)
teller ! (self, 23)
for (i <- 0 to 2) {
receive {
case (number, result) => println(number + " is primer? "+ result)
}
}
teller ! "quit"
}
}
recieve和recieveWithin方法
receive()方法接收一个函数值/闭包,返回一个处理消息的应答。
package com.fanshadoop.thread
import scala.actors.Actor
import Actor._
object Receive {
def main(args: Array[String]): Unit = {
val caller = self
val accumulator = actor {
var sum = 0
var continue = true
while (continue) {
sum += receive {
case number:Int => number
case "quit" => continue = false;0
}
}
caller ! sum
}
accumulator ! 1
accumulator ! 2
accumulator ! 10
accumulator ! "quit"
receive {
case result => println("total sum is " + result)
}
}
}
receive()方法会造成阻塞,直到实际接收到应答为止。receiveWithin()方法修正了这一点,它会接收一个timeout参数。
package com.fanshadoop.thread
import scala.actors._
import Actor._
object Receive {
def main(args: Array[String]): Unit = {
val caller = self
val accumulator = actor {
var sum = 0
var continue = true
while (continue) {
sum += receiveWithin(1000) {
case number:Int => number
case TIMEOUT => continue = false;0
}
}
caller ! sum
}
accumulator ! 1
accumulator ! 2
accumulator ! 10
accumulator ! "quit"
receiveWithin(1000) {//保持主线程能然活着
case result:Int => println("total sum is " + result)
case TIMEOUT => println("timeout")
}
}
}
在给定的超时期限内,如果什么都没有收到,receiveWithin()方法会收到一个TIMEOUT消息。如果不对其进行模式匹配,就会抛出异常。
react和reactWithin方法
在每个actor里,调用receive()的时候实际上会要求有一个单独的线程。这个线程会一直持有,知道这个actor结束。即使在等待消息到达,程序也会持有这些线程。如果在调用序列里没有需要保持和返回的状态,scala机会就可以从线程池里获取任意线程执行消息处理,这就是react()所做的事情。
react()不同于其表亲receive(),它不返回任何结果。调用receive()后,就回执行紧跟着这个调用的代码。不过react()则不同,放在调用后的任何代码都是不可达的。把调用react()想象成调用它的线程在调用之后就回立即释放。接收到一个消息后,如果可以匹配到react()方法里一个case语句,就会从线程池分配一个线程,执行这个case体。这个线程会一直运行,直到有另一个react()调用,或者是case语句中么有代码可执行了,此时线程返回,处理其他消息,或是去做虚拟机分配的其他任务。
package com.fanshadoop.thread
import scala.actors.Actor._
import scala.actors._
object React {
def info(msg:String) = println(msg + "recieved by " + Thread.currentThread())
def recieveMessage(id:Int) = {
for (i <- 1 to 2) {
receiveWithin(1000) {
case msg:String => info("msg"+id)
case TIMEOUT => println("timeout")
}
}
}
def reactMessage(id:Int) = {
for (i <- 1 to 2) {
react {
case msg:String => info("msg"+id)
}
println("-------------------")//代码不可达
}
}
def main(args: Array[String]): Unit = {
val actors = Array(
actor {info("react 1 message created");reactMessage(1)},
actor {info("react 2 message created");reactMessage(2)},
actor {info("receive 3 message created");recieveMessage(3)},
actor {info("receive 4 message created");recieveMessage(4)}
)
Thread.sleep(2000)
for (i <- 0 to 3) {
actors(i) ! "hello";Thread.sleep(2000)
}
Thread.sleep(2000)
for (i <- 0 to 3) {
actors(i) ! "hello";Thread.sleep(2000)
}
}
}
loop和loopwhile
相比于在reactWithin()里递归的调用方法,可以在loop()调用里放一个对reactWithin()的调用。执行loop方法的线程遇到reactWithin的调动时,会放弃控制。消息到达时,任意的线程都可以继续执行适当的case语句。case语句执行完毕,线程会继续回到loop块的顶部。这会一直继续下去。loopwhile方法时类似的,但只有提供的参数是有效的,它才会继续循环。
因为loopwhile方法负责处理循环,可以把局部状态放到循环之外,在reactwithin方法里访问它。这样既可以像receiveWithin那样处理状态,又可以像reactwithin那样利用来自线程池的线程
控制线程执行
使用receive时,每个actor运行在自己的线程里,react让actor共享来自线程池的线程。通过SingleThreadedScheduler,可以让scala在主线程里运行actor。
package com.fanshadoop.thread
import scala.actors.Actor
import scala.actors.scheduler.SingleThreadedScheduler
trait RunInMainThread extends Actor {
override def scheduler() = {
new SingleThreadedScheduler
}
}
class MyActor1 extends RunInMainThread {//在主线程中运行
def act() = {
println("MyActor1 running in " + Thread.currentThread())
}
}
class MyActor2 extends Actor {//在自己的线程中运行
def act() = {
println("MyActor2= running in " + Thread.currentThread())
}
}
object InMainThread {
def main(args: Array[String]): Unit = {
println("Main= running in " + Thread.currentThread())
new MyActor2 start
new MyActor1 start
}
}
在各种接受方法中的选择
首先应该优先使用Within结尾的方法,而不是其他的方法。调用receive()或react()可能会导致失败,actor可能会为一个接受不到的消息永远等下去,因为发消息的actor可能已经退出。因此,使用receivewithin或reactwithin,以便在一段合理的时间内没有收到响应之后,能够优雅的恢复过来。