2011-11-17 230 views
0

我讀過,使用反應時,所有演員都可以在單個線程中執行。我經常並行處理一個集合,並需要輸出結果。我不相信System.out.println是線程安全的,所以我需要一些保護。一種方法(傳統方式),我可以這樣做:使用斯卡拉演員的麻煩

val lock = new Object 
def printer(msg: Any) { 
    lock.synchronized { 
    println(msg) 
    } 
} 

(1 until 1000).par.foreach { i => 
    printer(i) 
} 

println("done.") 

這是如何第一個解決方案比較,在效率方面使用的演員?是不是真的,我沒有創建一個新的線程?

val printer = actor { 
    loop { 
    react { 
     case msg => println(msg) 
    } 
    } 
} 

(1 until 10000).par.foreach { i => 
    printer ! i 
} 

println("done.") 

但是,它似乎不是一個好的選擇,因爲演員代碼永遠不會完成。如果我在底部放置println,它將永遠不會被擊中,即使它看起來像是經歷了每次迭代i。我究竟做錯了什麼?

+0

有時,即使執行完成,Scala終端也會變得有趣並且「掛起」。不完全確定原因,但在輸出所有值後必須按Enter鍵。 –

+0

我希望最終會顯示一個'println(「done。」)「,但它不是('2.9.1.r0-b20110831114755')。 – schmmd

+0

就好像在打印大部分數字之前「正在完成」一樣。嘗試從(1到10)運行,你應該得到相同的結果。至於這種行爲的原因,演員是異步的。消息被髮送,循環退出,並且在演員處理所有消息之前,程序的其餘部分結束。 –

回答

1

就像你現在用你的Actor代碼一樣,你只有一個actor做所有的打印。正如您從運行代碼中看到的那樣,這些值都是由Actor按順序打印出來的,而在並行收集代碼中,這些值是無序的。我對並行集合不太熟悉,所以我不知道兩者之間的性能提升。然而,如果你的代碼並行地做了很多工作,你可能會想要和多個actor一起去。你可以這樣做:

def printer = actor { 
    loop { 
    react { 
     case msg => println(msg) 
    } 
    } 
} 

val num_workers = 10 
val worker_bees = Vector.fill(num_workers)(printer) 

(1 until 1000).foreach { i => 
    worker_bees(i % num_workers) ! i 
} 

def是很重要的。這樣你實際上創造了多個演員,而不僅僅是淹沒一個演員。

+0

感謝提醒與演員的訂單差異。 – schmmd

0

我不確定我能否正確理解您的問題。對我來說,你的演員代碼工作正常並終止。

不過,你可以使用savely爲println並行收集,所以你真正需要的是這樣的:

(1 until 1000).par.foreach { println(_) } 

作品就像這裏的魅力。我假設你已經知道輸出順序會有所不同,但我只是想再次強調一下,因爲問題經常出現。所以不要指望數字以連續的方式向下滾動屏幕。

+0

你是否知道,如果你保證兩條消息永遠不會互相印刷?我有經驗*某處*(誰知道在哪裏)平行printlns有時會穿插在中間字符串中(我明白字符串可能會交替,但不同字符串中的字符不應該穿插)。換句話說,我不確定OutputStream是否是線程安全的。 – schmmd

+0

@Neil聲稱println在其內部有一個鎖。 – schmmd

1

要解決您的演員實施問題,您需要告訴演員在程序退出前退出。

val printer = actor { 
    loop { 
    react { 
     case "stop" => exit() 
     case msg => println(msg) 
    } 
    } 
} 

(1 until 1000).par.foreach { printer ! _ } 

printer ! "stop" 

在這兩個你的例子有線程池的備份涉案雙方的相似之處庫和演員庫,但根據需要創建它們。

但是,println是線程安全的,因爲它的內部確實有一個鎖。

(1 until 1000).par.foreach { println(_) } // is threadsafe 

至於性能,有很多因素。首先是從多線程正在競爭的鎖移動到僅由一個線程(一個參與者)使用的鎖將提高性能。其次,如果您打算使用演員並想要表演,請使用 Akka。與斯卡拉演員相比,阿卡演員速度非常快。另外,我希望println寫入的標準輸出是文件而不是屏幕,因爲涉及顯示驅動程序將會導致性能下降。

使用parallels庫對於性能來說非常棒,因爲您可以利用多核來進行計算。如果每個計算都非常小,則嘗試使用演員路線進行集中報告。但是,如果每個計算都很重要,並且需要大量的CPU時間,那麼堅持使用println本身。你真的沒有處於鎖定狀態。

+0

我想你想在這種情況下使用val而不是def,否則你會告訴新創建的actor停止。 –

+0

@尼爾,謝謝你的出色答案。所以我原來的例子中的演員是在自己的線程中運行?演員管理什麼鎖?停止消息是我的應用程序沒有終止的原因,但奇怪的是沒有它,後面的'println(「done。」)'(在我的答案中更新)永遠不會顯示。 – schmmd

1

一個actor實例永遠不會處理多個消息。無論爲演員分配了哪個線程池,每個演員實例當時只會佔用一個線程,因此您可以保證所有打印都將被連續處理。

至於沒有完成,一個演員的執行從未從reactloop返回,所以:

val printer = actor { 
    loop { 
    react { 
     case msg => println(msg) 
    } 
    // This line is never reached because of react 
    } 
    // This line is never reached because of loop 
} 

如果您有while環和receive更換loopreact,你會看到while循環內的所有內容按預期執行。