第70回Kotlin dojoを開催した

今週は第70回を開催しました!

引き続きKotlin Hands-onをすすめています。

 

前回の様子はコチラ↓ 

kdnakt.hatenablog.com

 

 

[第70回の様子]

2021/6/30に第70回を開催した。

 

今週はちょっと人が減って、自分をいれて3名での開催。

PC不調のため自分は当面ナビゲータ役で参加。

 

勉強会本編の内容としては、Kotlin/Native Concurrencyハンズオンの続きを実施した。第5章Backgroundの後半を完了し、第6章Debuggingを半分ほど進めた。

 

[学んだことや疑問点]

  • Kotlin/Native Concurrency: 5. Background
    • Workerを利用する際にfreeze()するのはローカル変数の場合はまだ理解しやすいが、意図した以上の範囲をキャプチャした場合に問題となりやすい
    • 以下のようなcaptuerTooMuch()を実行してみる
class CountingModel{
    var count = 0

    fun increment(){
        count++
        background {
            saveToDb(count)
        }
    }

    private fun saveToDb(arg:Int){
        //Do some db stuff
        println("Saving $arg to db")
    }
}

fun captureTooMuch(){
    val model = CountingModel()
    model.increment()
    println("I have ${model.count}")

    model.increment()
    println("I have ${model.count}") //We won't get here
}
    • CountingModelオブジェクトを作成して、increment()を2回呼び出す:内部的にbackgroundでオブジェクトのプロパティをfreezeしているので、2回目の呼び出しはInvalidMutabilityExceptionが発生して失敗する
    • わかりやすくするために、captureTooMuchAgain()を実行してみる
fun captureTooMuchAgain(){
    val model = CountingModel()
    println("model is frozen ${model.isFrozen}") // false
    model.increment()
    println("model is frozen ${model.isFrozen}") // true
}
    • increment()を呼び出すとCountingModelのプロパティのcountがfreezeされるのだが、上記の結果からわかるように、親クラスのCountingModel自体もfreezeされている
    • これを避けるためには、background呼び出しを関数化して、関数に渡す引数のみをfreezeするようにすればよい:以下のようにすると、argはメモリ上countプロパティとは別になるので、親クラスがfreezeされることもない
fun captureArgs(){
    val model = CountingModelSafer()
    model.increment()
    println("I have ${model.count}")

    model.increment()
    println("I have ${model.count}")
}

class CountingModelSafer{
    var count = 0

    fun increment(){
        count++
        saveToDb(count)
    }

    private fun saveToDb(arg:Int) = background {
        println("Doing db stuff with $arg, in main $isMainThread")
    }
}
    • という内容を踏まえて、実験を行ってみた:saveToDbの引数を参照型にしたらどうなるか
    • 用意したのは以下のようなコード。increment()を2回呼び出すのは変わらず
class CountingModelSafer {
    var state = InternalState(0)    

    fun increment() {
        state.count++
        saveToDb(state)
    }
    private fun saveToDb(arg: InternalState) = background {
        println("Doing db stuff with ${arg.count}, in main $isMainThread")
    }
}

data class InternalState(var count: Int) 
    • 上記の場合には、やはり我が友InvalidMutabilityExceptionが発生した
    • しかし、以下の変更を加えると、問題なく動作した
    fun increment() {
        state.count++
        saveToDb(state.clone()) // シャロークローンで別オブジェクトに
    }
    • シャロークローンだから、うまくいったが、プロパティに参照型が含まれる場合は?とさらに疑問が湧いたので実験してみる
    • 用意したのは以下のようなクラス。(他の部分は省略、プルリクエストを参照。deepCopy()メソッドも実装されている)
data class InternalState(var count: Int, val state2: InternalState2) 
    • この場合はclone()してもダメで、clone時に追加でInternalState2をcloneすると、例外が発生しなくなった。
    • 結論として、この手のメモリ管理はKotlinであまりやりたくないね、という話になった
  • Kotlin/Native Concurrency: 6. Debugging
    • 並列処理で頻繁に出会うInvalidMutabilityExceptionのデバッグのために、ensureNeverFrozen()メソッドが利用できる
    • freeze()したあとにensureNeverFrozen()を呼ぶか、ensureNeverFrozen()したあとにfreeze()を呼び出すと、それぞれ2回目のメソッド呼び出し時にInvalidMutabilityExceptionが発生する
    • モブで話し合ったところ、これは本番運用するコードに埋め込むものなのか?それともデバッグ用に利用するものなのか?という点については結論がでなかった。悩ましい。

 

[まとめ]

モブプログラミング・スタイルで、Kotlin/Native Concurrencyハンズオンを進めた。

多少Native Concurrencyの理解が進んだ……気がする。

 

今週のプルリクエストはこちら。

github.com