這回繼續研究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 versionreceive { 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版本對比一下,相信你會有所收穫的。