今週は第68回を開催しました!
引き続きKotlin Hands-onをすすめています。
前回の様子はコチラ↓
[第68回の様子]
2021/6/16に第68回を開催した。
今週はずいぶんと久しぶりのメンバーも参加してくれて、自分をいれて5名だった。
今週もMacBookが不調のためナビゲータ役で参加。もはや画面シェアしない普通のミーティングでも厳しくなってきた。M1...。
勉強会本編の内容としては、Kotlin/Native Concurrencyハンズオンの続きを実施した。第4章Global Stateを終わらせ、第5章Backgroundを2割くらい終わらせた。
[学んだことや疑問点]
- Kotlin/Native Concurrency: 4. Global State
// グローバルなobjectはfrozen
object DefaultGlobalState{
var i = 5
}
fun cantChangeThis(){
println("i ${DefaultGlobalState.i}")
DefaultGlobalState.i++ // kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen sample.DefaultGlobalState@c40dcd8
println("i ${DefaultGlobalState.i}") // ここにはたどりつかない
}
- 上記のコードはコンパイルできるが、実行時にエラーとなる
- Kotlin 1.4では警告が出るらしい(このハンズオンはKotlin 1.3で実装されている:勉強会のあとで探したけれど該当する記事が見つけられなかった)
- 実際に自分の手元の別のプロジェクトで動かしてみたら以下のような警告が出た(こちらはKotlin 1.4を利用):objectのvarプロパティを宣言した箇所にエラーが出たので、実際にアクセスするかどうかは関係なさそう
> Task :compileKotlinNative w: /kmonkey/src/nativeMain/kotlin/repl/repl.kt: (38, 5): Variable in singleton without @ThreadLocal can't be changed after initialization

- グローバルなobjectを可変にするには@ThreadLocalアノテーションを付与すればよい
- ただしその場合、各スレッドごとにコピーしたオブジェクトが参照される:別スレッドからアクセスするとプロパティの値が異なる
@ThreadLocal
object ThreadLocalGlobalState{
var i = 5
}
fun threadLocalDifferentThreads(){
println("main thread: i ${ThreadLocalGlobalState.i}") // 5
ThreadLocalGlobalState.i++
println("main thread: i ${ThreadLocalGlobalState.i}") // 6
background { // 5章で詳細な説明があるが、本ハンズオン独自実装の別スレッド処理
println("other thread: i ${ThreadLocalGlobalState.i}") // 5
}
}
- globalでないcompanion objectでも変更できない点は同じ。
class GlobalStateCompanion{
companion object {
var i = 5
}
}
fun companionAlsoFrozen(){
println("i ${GlobalStateCompanion.i}")
GlobalStateCompanion.i++ // InvalidMutabilityExceptionが発生
println("i ${GlobalStateCompanion.i}") // ここは実行されない
}
- property:メインスレッドからのみアクセス可能、かつ可変
- 別スレッドからアクセスすると、
IncorrectDereferenceExceptionが発生する
data class SomeMutableData(var i:Int)
var globalCounterData = SomeMutableData(33)
fun globalCountingFail(){
background {
try {
globalCounterData.i++
println("count ${globalCounterData.i}")
} catch (e: Exception) {
e.printStackTrace() // kotlin.native.IncorrectDereferenceException: Trying to access top level value not marked as @ThreadLocal or @SharedImmutable from non-main thread
}
}
}
- この制約は、モバイルのネイティブアプリ開発時にUIをメインスレッドからしか更新できないのと似ている
- グローバルプロパティにも@ThreadLocalアノテーションをつけられる:各スレッドがコピーを保持するのも同じ。
- @SharedImmutableアノテーションをつけると、object同様にfrozenなプロパティとして扱える
- Kotlin/Native Concurrency: 5. Background
- Kotlin/Nativeで並列処理はWorkerを使う:Javaのスレッドのようなもの
- 第4章で扱ったbackground関数は以下のようになっている
import kotlin.native.concurrent.Future
import kotlin.native.concurrent.TransferMode
import kotlin.native.concurrent.Worker
import kotlin.native.concurrent.freeze
fun background(block: () -> Unit) {
val future = worker.execute(TransferMode.SAFE, { block.freeze() }) {
it()
}
collectFutures.add(future)
}
private val worker = Worker.start()
private val collectFutures = mutableListOf<Future<*>>()
- Worker.executeの第2引数は、第3引数の関数を呼び出す際のパラメータを生成する
- その値がメインスレッドから参照されていない場合は可変になるが、そうでなければfrozenになる
- 可変の状態を別スレッドに渡すことも不可能ではないが、Kotlin/Nativeでは基本的にfrozenになる
[まとめ]
モブプログラミング・スタイルで、Kotlin/Native Concurrencyハンズオンを進めた。
Kotlin/Nativeではグローバルなobject/propertyの扱いも特殊になるということがわかった。
今週はプルリクエストなし。