2011-11-17 99 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循环内的所有内容按预期执行。