不要將Actors用於併發程式設計
將Scala/AKKA的Actor用於併發程式設計是一種反模式,相反,應該使用Actor模型守護狀態,使用future實現併發,來自Don't use Actors for concurrency一文提出了自己獨特觀點:
在Scala領域,一個通用實踐是使用Actor實現併發,這是受到Akka和許多Scala文章的影響,這些文章都是高度圍繞Actor為中心(actor-centric),這是一種壞的實踐,應該被認為是一種反模式,Actor不應該被作為流程控制或併發的工具,它們只是對以下兩種目標適合:維護狀態和提供訊息端點,在其他場合最好可能使用Futrue.
在別的地方,FooActor是如下使用:
關鍵需要注意到FooActor並沒有任何可變狀態,FooActor內部沒有任何屬性欄位等,它只是接受一個訊息,這個FooActor繼承了actor,Akka沒有選擇地以單執行緒方式執行這段程式碼。
再比較另外一種寫法,下面程式碼沒有使用Actor,而是使用了Future
呼叫程式碼如下:
這段使用future而不是Actor的好處是大大提高併發性。
如果我們使用Actor,考慮以下併發使用場景:
這段程式碼future r1和r2原則上應該是並行執行,它們應該是單獨計算的,但是因為fooActor是單執行緒的緣故,這種計算也是單執行緒的,相反,下面使用Future的計算是多執行緒的:
第二個好處是安全,後者使用使用typed actors實現的。
不熟悉Akka future的人可能會問,上面程式碼呼叫為什麼不能使用如下方式?
前者能夠並行執行,而後者只能序列化序列執行,後者程式碼等同於:
前者很清晰,myFooRequester.fooResult(request2) 只有在result1可用時才會取值。
該文還指出使用多個Actor然後放在router後面,這樣做增加了複雜性。
該文透過程式碼展示如何使用Actor實現狀態改變,這是因為Actor單執行緒操作狀態的原因。
還透過程式碼展示了Actor作為訊息端點的使用,最好的案例是Spray Routing。
更多可參考原文。
在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) } } } <p class="indent"> |
在別的地方,FooActor是如下使用:
val fooResult: Future[Any] = fooActor ? FooRequest(...) <p class="indent"> |
關鍵需要注意到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) } } <p class="indent"> |
呼叫程式碼如下:
val fooResult: Future[FooResponse] = myFooRequester.fooResult(FooRequest(...)) <p class="indent"> |
這段使用future而不是Actor的好處是大大提高併發性。
如果我們使用Actor,考慮以下併發使用場景:
val r1 = fooActor ? request1 val r2 = fooActor ? request2 for { result1 <- r1 result2 <- r2 } yield (combination(result1.asInstanceOf[FooResponse], result2.asInstanceOf[FooResponse])) <p class="indent"> |
這段程式碼future r1和r2原則上應該是並行執行,它們應該是單獨計算的,但是因為fooActor是單執行緒的緣故,這種計算也是單執行緒的,相反,下面使用Future的計算是多執行緒的:
val r1 = myFooRequester.fooResult(request1) val r2 = myFooRequester.fooResult(request2) for { result1 <- r1 result2 <- r2 } yield (combination(result1, result2)) <p class="indent"> |
第二個好處是安全,後者使用使用typed actors實現的。
不熟悉Akka future的人可能會問,上面程式碼呼叫為什麼不能使用如下方式?
for { result1 <- myFooRequester.fooResult(request1) result2 <- myFooRequester.fooResult(request2) } yield (combination(result1, result2)) <p class="indent"> |
前者能夠並行執行,而後者只能序列化序列執行,後者程式碼等同於:
myFooRequester.fooResult(request1).flatMap( result1 => myFooRequester.fooResult(request2).flatMap( result2 => combination(result1, result2) ) ) <p class="indent"> |
前者很清晰,myFooRequester.fooResult(request2) 只有在result1可用時才會取值。
該文還指出使用多個Actor然後放在router後面,這樣做增加了複雜性。
該文透過程式碼展示如何使用Actor實現狀態改變,這是因為Actor單執行緒操作狀態的原因。
還透過程式碼展示了Actor作為訊息端點的使用,最好的案例是Spray Routing。
更多可參考原文。
相關文章
- 併發程式設計程式設計
- 【Java併發程式設計】併發程式設計大合集-值得收藏Java程式設計
- 使用GPars實現JVM併發和Actors模型JVM模型
- Go 併發程式設計 - 併發安全(二)Go程式設計
- Golang 併發程式設計Golang程式設計
- 併發程式設計(四)程式設計
- 併發程式設計(二)程式設計
- java 併發程式設計Java程式設計
- 併發程式設計13程式設計
- golang併發程式設計Golang程式設計
- Java併發程式設計Java程式設計
- Go 併發程式設計Go程式設計
- shell併發程式設計程式設計
- Scala併發程式設計程式設計
- 併發程式設計 synchronized程式設計synchronized
- 併發程式設計喚醒判斷用while程式設計While
- 併發程式設計和並行程式設計程式設計並行行程
- 如何將golang的併發程式設計運用到實際開發Golang程式設計
- Java併發程式設計-鎖及併發容器Java程式設計
- 併發程式設計之:JUC併發控制工具程式設計
- Java併發系列—併發程式設計挑戰Java程式設計
- 【Java併發程式設計】一、為什麼需要學習併發程式設計?Java程式設計
- Java併發程式設計 - 第十一章 Java併發程式設計實踐Java程式設計
- java-併發程式設計Java程式設計
- 併發程式設計前傳程式設計
- 併發程式設計導論程式設計
- C# 併發程式設計C#程式設計
- Java併發程式設計-CASJava程式設計
- 併發程式設計之:ForkJoin程式設計
- 併發程式設計之:JMM程式設計
- 併發程式設計之:synchronized程式設計synchronized
- 併發程式設計之:Lock程式設計
- 併發程式設計之:CountDownLatch程式設計CountDownLatch
- Java併發程式設計:synchronizedJava程式設計synchronized
- Java 併發程式設計解析Java程式設計
- 併發程式設計進階程式設計
- 併發程式設計概覽程式設計
- Java併發程式設計:LockJava程式設計