Kotlin Nativeと並行処理

Photo by David Wirzba on Unsplash

Kotlin Nativeの並行処理では制限がいくつかあります。そのあたりに色々調べてみたので、まとめておきたいと思います。この問題に対応した新しいメモリマネージャーが現在Experimentalで使用できますが、今回は使っていない状態のものになります。

新しいメモリマネージャーがすでありますが、現状を把握しておきたかったので今回調べました。新しいメモリマネージャーはまた別途調査したいと思います。

並行処理におけるルール

Kotin Nativeにおいて並行処理をする際のルールがあります。

  • Mutable Stateは1スレッドのみ
  • Immutable Stateは複数スレッド可能

Mutable Stateは1つのスレッドでしか扱えません。逆にImmutable Stateは複数スレッドから扱えます。

Kotlin NativeじゃなくてもMutable Stateを並行処理で扱うと問題が起きやすいです。

Kotlin Nativeにおいては並行処理で状態を扱うときはImmutable Stateであることを保証するために、Frozenというランタイム状態を定義しています。

Frozen

Kotlin Nativeにおいて並行処理で状態を扱うときは必ずFrozen状態にする必要があります。

Frozenのものは変更不可になり、変更しようとすると InvalidMutabilityException が発生します。

Frozen状態にするには、 freeze メソッドを使うだけです。

オブジェクトがFrozenかは isFrozen を使って知ることができます。

freeze メソッドは対象のオブジェクトが参照してるオブジェクトも推移的にすべてFrozen状態にします。

Frozen状態は元に戻すことはできません。

Worker

Kotlin Nativeで実際に並行処理を見ていきたいと思います。Kotlin Nativeでは Worker という低レベルAPIが用意されているので、これを使っていきます。

使い方は以下のようになっています。

しかし、これを実行しても IllegalStateException が発生します。

この例だと foo という状態を入力に使っていますが、このとき foo はFrozen状態ではありません。Workerは渡された状態がFrozenかをチェックしているため、ここではエラーになってしまいます。

では、どうすればよいのか言うと、 foo をFozenにすればよいだけです。

これでエラーが発生せず実行できます。

もちろんFrozenなオブジェクトなので別スレッドで input.i = 2 のような変更処理はできません。

Global State

グローバルな状態においてはいくつか自動的にルールが適用され、扱いに注意が必要です。

object

object で定義されたものは自動的にFrozen状態になります。もちろんFrozenなので変更しようとすると InvalidMutabilityException が発生します。

object@ThreadLocal をつけることでFrozen状態は解除されます。

ただし、この状態で別スレッドで実行する場合は更に注意が必要です。

これを実行すると、メインスレッドと別スレッドでは、結果が異なっています。@ThreadLocal を使うと別スレッドで扱うときはコピーが作成されるためメインスレッドとは別のものになります。

companion object についても同様の扱いになります。

Global properties

グローバル変数にも制限があります。

グローバル変数を別スレッドからアクセスすると IncorrectDereferenceException が発生します。状態変更しなくても参照するだけでエラーになります。

これに対応するには2つ方法があります。

まずは object と同じようにグローバル変数に @ThreadLocal をつけます。もちろん別スレッドにおいてはコピーになるため別の値になります。

もう一つは、 @SharedImmutable をつけます。これで別スレッドから参照できるようになりますが、状態の変更はできません。

ensureNeverFrozen

ensureNeverFrozen を使うとオブジェクトがFrozen状態にされてる場合や、Frozenされたタイミングでエラーにすることができます。これを使うとFrozenされた原因などを調べることができます。

Atomic

Frozen状態でも値の変更が可能な Atomic クラスが提供されています。 AtomicInt , AtomicLong , AtomicReference があります。

AtomicInt を使った例です。

Coroutines

これまで低レベルAPIの Worker 使っていましたが、高レベルAPIとしてはCoroutinesを使っていくと思います。

native-mt

現在、通常のCoroutinesはKotlin Nativeではマルチスレッドにはならずシングルスレッドとして実行されます。マルチスレッドに対応した native-mt という特別なバージョンがあるので、そちらを使っていくことになります。

スレッドの確認

動作確認をするために今どのスレッドで実行しているかを確認する方法を書いておきます。

Stately というライブラリを使います。

これを使って以下のようなプロパティを定義します。この isMainThread というのでメインスレッドで実行されているかを確認できます。

これはハンズオンのコードから持ってきています。

native-mtではないバージョンの動作確認

native-mt ではないバージョンを使ってみます。

結果から見てわかるように、 Dispatchers.Default を使っていてもメインスレッドで実行されています。また、オブジェクトはFrozen状態にはなっていません。

native-mtバージョンの動作確認

native-mt を使った場合は、別スレッドで実行されているのが確認できます。ただし、参照してるオブジェクトは自動的にFrozen状態になっているので注意が必要です。これは仕様になっています。

また戻り値にも同様にFozenになっています。

--

--

--

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Kenji Abe

Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO

More from Medium

Kotlin Cookbook 1

Extension Functions in Kotlin

Kotlin When Expression

Android Protobuf with kotlin and Wire