将Scala/AKKA的Actor用于并发编程是一种反模式,相反,应该使用Actor模型守护状态,使用future实现并发,来自Don't use Actors for concurrency一文提出了自己独特观点:
在Scala领域,一个通用实践是使用Actor实现并发,这是受到Akka和许多Scala文章的影响,这些文章都是高度围绕Actor为中心(actor-centric),这是一种坏的实践,应该被认为是一种反模式,Actor不应该被作为流程控制或并发的工具,它们只是对以下两种目标适合:维护状态和提供消息端点,在其他场合最好可能使用Futrue.
反模式
下面展示一下坏的使用方式代码:
class FooActor extends Actor { def receive = { case (x:FooRequest) => { val x = database.runQuery("SELECT * FROM foo WHERE ", x) val y = redis.get(x.fookey) sender ! computeResponse(x,y) } } }
|
在别的地方,FooActor是如下使用: val fooResult: Future[Any] = fooActor ? FooRequest(...)
|
关键需要注意到FooActor并没有任何可变状态,FooActor内部没有任何属性字段等,它只是接受一个消息,这个FooActor继承了actor,Akka没有选择地以单线程方式运行这段代码。再比较另外一种写法,下面代码没有使用Actor,而是使用了Future
class FooRequester(system: ActorSystem) { import system.dispatcher
def fooResult(x: FooRequest): Future[FooResponse] = Future { val x = database.runQuery("SELECT * FROM foo WHERE ", x) val y = redis.get(x.fookey) computeResponse(x,y) } }
|
调用代码如下:
val fooResult: Future[FooResponse] = myFooRequester.fooResult(FooRequest(...))
|
这段使用future而不是Actor的好处是大大提高并发性。
如果我们使用Actor,考虑以下并发使用场景:
val r1 = fooActor ? request1 val r2 = fooActor ? request2 for { result1 <- r1 result2 <- r2 } yield (combination(result1.asInstanceOf[FooResponse], result2.asInstanceOf[FooResponse]))
|
这段代码future r1和r2原则上应该是并行运行,它们应该是单独计算的,但是因为fooActor是单线程的缘故,这种计算也是单线程的,相反,下面使用Future的计算是多线程的:
val r1 = myFooRequester.fooResult(request1) val r2 = myFooRequester.fooResult(request2) for { result1 <- r1 result2 <- r2 } yield (combination(result1, result2))
|
第二个好处是安全,后者使用使用typed actors实现的。
不熟悉Akka future的人可能会问,上面代码调用为什么不能使用如下方式?
for { result1 <- myFooRequester.fooResult(request1) result2 <- myFooRequester.fooResult(request2) } yield (combination(result1, result2))
|
前者能够并行运行,而后者只能序列化串行运行,后者代码等同于: myFooRequester.fooResult(request1).flatMap( result1 => myFooRequester.fooResult(request2).flatMap( result2 => combination(result1, result2) ) )
|
前者很清晰,myFooRequester.fooResult(request2) 只有在result1可用时才会取值。
该文还指出使用多个Actor然后放在router后面,这样做增加了复杂性。
该文通过代码展示如何使用Actor实现状态改变,这是因为Actor单线程操作状态的原因。
还通过代码展示了Actor作为消息端点的使用,最好的案例是Spray Routing。
更多可参考原文。