Scala的actor提供了一种基于事件的轻量级线程。只要使用scala.actors.Actor伴生对象的actor()方法,就可以创建一个actor。它接受一个函数值/闭包做参数,一创建好就开始运行。用!()方法给actor发消息,用receive()方法从actor接收消息。receive()也可以闭包为参数,通常用模式匹配处理接收到的消息。
我们看个例子,假定我们需要判定一个给定的数是完全数:
def sumOfFactors(number: Int) = {
(0 /: (1 to number)) { (sum, i) => if (number % i == 0) sum + i else sum }
}
def isPerfect(candidate: Int) = 2 * candidate == sumOfFactors(candidate)
这段代码按顺序计算了给定candidate数的因子之和。这段代码有个问题。如果数值很大,顺序执行会非常慢。而且,如果在多核处理器上运行这段代码,也利用不到多核的优势。不管在什么时候,都是一个核做了所有艰苦的工作,没有用到其他核。
对这种运算密集型操作而言,在任意时刻,都只有一个核有效地利用了起来。或者,可以将其视为双核的能力只用到一半。
将因子求和的计算划分到多线程上,可以获得更好的吞吐量。即便在只有一个处理器的机器上,应用也能获得更多的执行机会,得到更好的响应。
这样,将从1到candidate数这个范围内的数划分成多个区间,把每个区间内求和的任务分配给单独的线程。
import scala.actors.Actor._
object ActorTest {
def sumOfFactorInRange(lower: Int, upper: Int, number: Int) = {
(0 /: (lower to upper)) { (sum, i) => if (number % i == 0) sum + i else sum }
}
def isPerfectConcurrent(candidate: Int) = {
val RANGE = 1000000
val numberOfPartitions = (candidate.toDouble / RANGE).ceil.toInt
val caller = self
for (i <- 0 until numberOfPartitions) {
val lower = i * RANGE + 1
val upper = candidate min (i + 1) * RANGE
actor {
caller ! sumOfFactorInRange(lower, upper, candidate)
}
}
val sum = (0 /: (0 until numberOfPartitions)) { (partialSum, i) =>
receive {
case sumInRange: Int => partialSum + sumInRange
}
}
2 * candidate == sum
}
def main(args: Array[String]): Unit = {
println(isPerfectConcurrent(6))
println(isPerfectConcurrent(33550336))
println(isPerfectConcurrent(33550337))
}
}
上面代码里没有synchronized或是wait。在isPerfectConcurrent()方法里,先对这个范围内的值进行分区。在第16行,对于每个区间而言,因子部分求和的计算委托给单独的actor。当actor完成分配给它的任务,在第17行,他就会把部分和作为消息发给调用者(caller)。在这个闭包里,caller变量绑定到isPerfectConcurrent()方法里的一个变量——这个变量持有actor的引用,它是通过调用self()方法得到的,表示主线程。最后,在第22行,从委托的actor中接收消息,一次一个。用foldLeft()方法(这里显示为/😦)方法)接收了所有的部分和,以函数式风格计算了这些部分的总和。
下面对一个范围内的值查找完全数:
import scala.actors.Actor.{actor, receive, self}
object ActorTest02 {
def sumOfFactors(number: Int) = {
(0 /: (1 to number)) { (sum, i) => if (number % i == 0) sum + i else sum }
}
def isPerfect(candidate: Int) = 2 * candidate == sumOfFactors(candidate)
def sumOfFactorInRange(lower: Int, upper: Int, number: Int) = {
(0 /: (lower to upper)) { (sum, i) => if (number % i == 0) sum + i else sum }
}
def isPerfectConcurrent(candidate: Int) = {
val RANGE = 1000000
val numberOfPartitions = (candidate.toDouble / RANGE).ceil.toInt
val caller = self
for (i <- 0 until numberOfPartitions) {
val lower = i * RANGE + 1
val upper = candidate min (i + 1) * RANGE
actor {
caller ! sumOfFactorInRange(lower, upper, candidate)
}
}
val sum = (0 /: (0 until numberOfPartitions)) { (partialSum, i) =>
receive {
case sumInRange: Int => partialSum + sumInRange
}
}
2 * candidate == sum
}
def countPerfectNumbersInRange(start: Int, end: Int, isPerfectFinder: Int => Boolean) = {
val startTime = System.nanoTime()
val numberOfPerfectNumbers = (0 /: (start to end)) { (count, candidate) =>
if (isPerfectFinder(candidate)) count + 1 else count
}
val endTime = System.nanoTime()
println("Found " + numberOfPerfectNumbers +
" perfect numbers in given range, took " +
(endTime - startTime) / 1000000000.0 + " secs")
}
def main(args: Array[String]): Unit = {
val startNumber = 33550300
val endNumber = 33550400
countPerfectNumbersInRange(startNumber, endNumber, isPerfect) // Found 1 perfect numbers in given range, took 40.0017878 secs
countPerfectNumbersInRange(startNumber,endNumber,isPerfectConcurrent) // Found 1 perfect numbers in given range, took 27.4417483 secs
}
}
在countPerfectNumberInrange()里,统计了给定范围内从start到end之间能够发现多少个完全数。实际找出候选数是否是完全数的方法委托给闭包isPerfect-Finder——它是作为参数传进来的。在给定范围内查找完全数数量所花的时间由JDK的System.nanoTime()方法计算得到。调用countPerfectNumberInrange()两次,先使用顺序实现isPerfect(),然后用并发实现isPerfectConcurrent()。
同并发实现相比,顺序计算差不多多花了1/3的时间。