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を使う場合は十分気をつけましょう。