Actor Extensionはスレッドアンセーフな点に注意
以下の記事の通りActor Extensionを調べたのですが、1点注意点を見つけたので追記です。
結論:スレッドアンセーフ
ActorSystemごとに1インスタンス、全Actorから共通して使える、状態も持てる。という訳なので、やはり複数Actorから同時にアクセスされればレースコンディションが発生します。スレッドアンセーフです。
ダメなコード例
前回の例を少し変えて以下のようなコードを実行してみます。
import akka.actor._
import akka.pattern._
import akka.util.Timeout
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.postfixOps
object Main {
def main(args: Array[String]): Unit = {
implicit val timeout = Timeout(10 seconds)
val system1 = ActorSystem("my-system")
val actor1 = system1.actorOf(Props(classOf[MyActor]))
val actor2 = system1.actorOf(Props(classOf[MyActor]))
(0 until 10000) foreach { x =>
actor1 ! MyActor.Protocol.Increment
actor2 ! MyActor.Protocol.Increment
}
Thread.sleep(5000)
val count = Await.result(actor1 ? MyActor.Protocol.GetCount, Duration.Inf)
println(s"count = $count")
system1.terminate()
}
}
class MyActor extends Actor {
val counter = CountExtension(context.system)
def receive = {
case MyActor.Protocol.GetCount => sender() ! counter.get()
case MyActor.Protocol.Increment => counter.increment()
}
}
object MyActor {
object Protocol {
object Increment
object GetCount
}
}
class CountExtensionImpl extends Extension {
// private val counter = new AtomicLong(0)
private var counter = 0L
//def increment() = counter.incrementAndGet()
def increment() = {
counter += 1
counter
}
//def get() = counter.get()
def get() = counter
}
object CountExtension
extends ExtensionId[CountExtensionImpl]
with ExtensionIdProvider {
override def lookup = CountExtension
override def createExtension(system: ExtendedActorSystem) = new CountExtensionImpl
override def get(system: ActorSystem): CountExtensionImpl = super.get(system)
}
Actorを2つ作って、それぞれ10000回メッセージを送っています。これにより合計20000回incrementされて、最後のGetCountにより、20000がprintlnされるように見えます。が、実際はそうなりません。
count = 19975
こんな感じで実行するたびに値が異なり、20000になりません。競合していますね。
対策
今回の例だとAtomicLongを使うとか。そうでない場合は無難にsynchronizedでしょうか。GitHubでコード検索したらsynchronizedをちらほら見かけました。
という訳でActorを使っているとついついレースコンディション周りをおろそかにしがちですが、Extensionを使う場合は十分気をつけましょう。