LoginSignup
2
2

More than 3 years have passed since last update.

Kotlin 1.3のCoroutineのコンテキストとディスパッチャ⑨(スレッドローカルデータ)

Posted at

検証環境

この記事の内容は、以下の環境で検証しました。

  • Intellij IDEA ULTIMATE 2018.2
  • Kotlin 1.3.0
  • Gradle Projectで作成
  • GradleファイルはKotlinで記述(KotlinでDSL)

準備

詳細は下記の準備を参照してください。
https://qiita.com/naoi/items/8abf2cddfc2cb3802daa

Thread-local data

前回に引き続き、公式サイトを読み解いていきます。

今回のタイトルは、スレッドないのローカルのデータだと思いますが、意味不明です。
早速読み進めましょう。

Sometimes it is convenient to have an ability to pass some thread-local data, but, for coroutines, which are not bound to any particular thread, it is hard to achieve it manually without writing a lot of boilerplate.

For ThreadLocal, asContextElement extension function is here for the rescue. It creates an additional context element, which keeps the value of the given ThreadLocal and restores it every time the coroutine switches its context.

It is easy to demonstrate it in action:

意訳込みですが(略)

時々、スレッドにデータを渡したいことがあります。しかし、特定のスレッドにバインドされていないコルーチンの場合は、簡単にはいきません。そのようなとき、thread-local dataが便利です。

ThreadLocalクラスのasContextElement拡張関数で実現が可能です。asContextElement拡張関数はコンテキスト要素を追加できます。コンテキスト要素に追加した値は、コンテキストが切り替わることリストアされます。

簡単なサンプルコードを確認してください。

ThreadLocalクラスのasContextElement拡張関数でコンテキストの任意の値が保持できるイメージでしょうか?
サンプルコードを確認してみます。

import kotlinx.coroutines.*

val threadLocal = ThreadLocal<String?>() // declare thread-local variable

fun main() = runBlocking<Unit> {
    threadLocal.set("main")
    println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
    val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
        println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
        yield()
        println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
    }
    job.join()
    println("Post-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
}

動作も確認してしまいます。

Pre-main, current thread: Thread[main,5,main], thread local value: 'main'
Launch start, current thread: Thread[DefaultDispatcher-worker-1,5,main], thread local value: 'launch'
After yield, current thread: Thread[DefaultDispatcher-worker-2,5,main], thread local value: 'launch'
Post-main, current thread: Thread[main,5,main], thread local value: 'main'

はじめに、ThreadLocalクラスのオブジェクトを生成してます。
ThreadLocal#setメソッドで、データに"main"を格納しているみたいです。
launch関数でasContextElement関数を呼び出し、データに"launch"を格納しています。
取り出すときは、ThreadLocal#getメソッドで値の取り出しができるみたいです。

結果をみるとmain処理のコンテキストとlaunchのコンテキストで保持しているデータがことなることと、処理がmainにもどってくると、mainのコンテキストの情報を表示しています。

結果的に、コンテキストごとにデータが保持できると考えられます。

続けて読み進めていきます。

In this example we launch new coroutine in a background thread pool using Dispatchers.Default, so it works on a different threads from a thread pool, but it still has the value of thread local variable, that we've specified using threadLocal.asContextElement(value = "launch"), no matter on what thread the coroutine is executed. Thus, output (with debug) is:

訳します。

サンプルコードでは、Dispatchers.Defaultを利用してバックグラウンドスレッドプールで新しいコルーチンを起動しています。このような場合でもthreadLocal#asContextElementで格納されたスレッドローカルデータは問題なく利用できます。
結果としてこのように出力されます。

なるほど、スレッドプールが違っても問題なく利用できるみたいです。

読み進めましょう。

ThreadLocal has first-class support and can be used with any primitive kotlinx.coroutines provides. It has one key limitation: when thread-local is mutated, a new value is not propagated to the coroutine caller (as context element cannot track all ThreadLocal object accesses) and updated value is lost on the next suspension. Use withContext to update the value of the thread-local in a coroutine, see asContextElement for more details.

Alternatively, a value can be stored in a mutable box like class Counter(var i: Int), which is, in turn, stored in a thread-local variable. However, in this case you are fully responsible to synchronize potentially concurrent modifications to the variable in this mutable box.

For advanced usage, for example for integration with logging MDC, transactional contexts or any other libraries which internally use thread-locals for passing data, see documentation for ThreadContextElement interface that should be implemented.

長いのでいつもより多めに意訳込みです。

ThreadLocalクラスは、first-classであるため、kotlinx.coroutines が提供しているサポートを受けることができます。ただし、1つだけ重要な制限を受けることになります。
それは、スレッドローカルが変更された場合、呼び出し元に変更されたことが通知されません。更新された情報は、次にサスペンド関数などで中断された際に削除されます。コルーチン内のThreadLocalの値を更新するにはwithContext関数を呼び出して下さい。詳細は、asContextElementを参照してください。

あるいは、値を下記のようなクラスに格納しておく方法もあります。
class Counter(var i: Int)
このオブジェクトはThreadLocal内に格納されます。ただし、このような値は、同期をとっているわけではないので、同期を取れるように扱う必要があります。

MDC、トランザクションコンテキスト、データを渡すだけのためにThreadLocalを利用するような高度な利用をする場合は、ThreadContextElementを参照してください。

なるほど、ThreadLocalが変わると流石にデータは引き継げないようです。
このようなケースがあるかどうかは、現時点で判断できませんが、気をつけるようにしておきます。

まとめ

このブロックで理解できたことは以下のことだと思います。

  • Thread内でデータの共有が可能であること
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2