这回继续研究Actor的应用,我发现scala-lang里关于Actor的Advance Example很有代表性,所以专门花时间研究一下这个例子,以下我经过我修正后的代码并且加入了一些关键的debug信息,因为原始的版本无法在Scala2.8上运行:
import scala.actors._ import scala.actors.Actor._ object Message { def main(args: Array[String]) { val n = 3 var k = 0 val nActors = 500 val finalSum = n * nActors def beh(next: Actor, sum: Int) { k = k+1 react { case value: Int => val j = value + 1; val nsum = sum + j println(Thread.currentThread().getId()+"nSum:"+nsum+", value:"+value+", sum:"+sum) if (next == null && nsum >= finalSum) { println("End"); System.exit(0) } else { if (next != null) next ! j //react never return, so must call some code to continue beh(next, nsum) } } } def actorChain(i: Int, a: Actor): Actor = if (i > 0) actorChain(i-1, actor(beh(a, 0))) else a val firstActor = actorChain(nActors, null) println("#call times:"+k); var i = n; while (i > 0) { firstActor ! 0; i -= 1 } println("Let's go!"); } }
这个例子的意图是实用多线程技术计算++操作,直到达到目标值finalSum为止。首先做的是递归的创建500个actor对象,使用的语法是actor{body}:Unit的方式,每一个actor都拥有前面创建那个的引用,这样可以测试两个actor互相发消息,就像击鼓传花的那种效果。
开始运行的时候,由firstActor开始,注意到这里初始同时向firstActor发送了3次消息,每次都是一个Int: 0,然后在接收消息的时候,我特意打印了当前线程的ID并记录了各个关键运算参数的值。可能令人费解的是react{}的使用,以及else里beh又递归的调用了自己。通常来说我们在actor里都是使用receive或者receiveWithin接收消息,但是这两种方式有个致命的弱点,就是每次接收一次消息都必须使用新的Thread,所以如果我们改用receive或者receiveWithin来接收消息,那么你会看到几百个Thread ID。下面给出使用receive和receiveWithin版本的代码片段:(注意receiveWithin必须要加上TIMEOUT的case,否则无法运行)
//receiveWithin receiveWithin(1000) { case value: Int => val j = value + 1; val nsum = sum + j println(Thread.currentThread().getId()+"nSum:"+nsum+", value:"+value+", sum:"+sum) if (next == null && nsum >= finalSum) { println("End"); System.exit(0) } else { if (next != null) next ! j } case TIMEOUT => System.exit(-1); } } //receive version receive { case value: Int => val j = value + 1; val nsum = sum + j println(Thread.currentThread().getId()+"nSum:"+nsum+", value:"+value+", sum:"+sum) if (next == null && nsum >= finalSum) { println("End"); System.exit(0) } else { if (next != null) next ! j } } }
所以出于性能考虑,这里就使用了react,因为它会使用一种更为合理的任务领取方式来申请Thread资源,多次消息的发送和接受都是由总的线程池来安排最优化的线程数量来执行。在笔者的机器上一共就看到4个不同的Thread ID。另外,使用react的一个特点是它每次执行完了case,不会return任何东西,也就是说代码不能在继续往下走。这个时候一个通常的做法都是递归的调用包含react的自身,这样就会回到react的起点,然后等待线程池安排任何一个Thread来领取任务,完成消息的处理。
由于使用了react,只用少量的Thread资源就反复的处理了众多的消息最后完成值计算超过finalSum成功End退出。在笔者的机器上call times的值是会变化而且多执行几次次数还相差不小。这是因为众多消息的处理是由随机的Thread对象来领取的,可能是由next ! j引起的,那么这个Thread就能获得当前beh对象的上下文以及sum, nSum的值;同时,也有可能是递归调用beh(next, nSum)引起的,那么这个Thread拿到的是另外一套上下文和变量值。总之谁先到finalSum,程序就结束。这个值可以通过笔者打印出来的log仔细分析一下,尤其是同receive和receiveWithin版本对比一下,相信你会有所收获的。