[KotlinCoroutine]サスペンド機能で@Synchronized修飾子を使用しないでください

ここに写真の説明を挿入
Kotlinを使用する場合、通常@Synchronizedはスレッド間の同期を使用します。したがって、コルチンに不慣れな多くの学生は@Synchronized、「コルチン間の同期」を実現するためにサスペンド機能ビューを追加します。これは効果的ですか?

1.协程+同期?


通常、コロチンは並列タスクの実行に役立ちます。

suspend fun doSomething(i: Int) {
    
    
    println("#$i enter critical section.")

    // do something critical
    delay(1000)

    println("#$i exit critical section.")
}

fun main() = runBlocking {
    
    
    repeat(2) {
    
     i ->
        launch(Dispatchers.Default) {
    
    
            println("#$i thread name: ${
      
      Thread.currentThread().name}")
            doSomething(i)
        }
    }
}

ログから、2つのタスクのenter合計がexit並行して出力され、順序がないことがわかります。

#0 thread name: DefaultDispatcher-worker-1
#1 thread name: DefaultDispatcher-worker-2
#0 enter critical section.
#1 enter critical section.
#1 exit critical section.
#0 exit critical section.

次に、@Synchronized試してみてください。

@Synchronized
suspend fun doSomething(i: Int) {
    
    
    println("#$i enter critical section.")

    // do something
    delay(1000)

    println("#$i exit critical section.")
}

fun main() = runBlocking {
    
    
    repeat(2) {
    
     i ->
        launch(Dispatchers.Default) {
    
    
            println("#$i thread name: ${
      
      Thread.currentThread().name}")
            doSomething(i)
        }
    }
}
#0 thread name: DefaultDispatcher-worker-2
#0 enter critical section.
#1 thread name: DefaultDispatcher-worker-1
#1 enter critical section.
#0 exit critical section.
#1 exit critical section.

通常の機能の場合、Synchronizedの追加により、2つのスレッドを順番に実行する必要がありますが、上記のログは、Synchronizedが追加されているかどうかに関係なく、中断された機能の場合、それらは並行して実行されます(enterexit同時に出力されます)。

書き方を変えて、サスペンド関数内にSynchronizedを追加してみましょう。

val LOCK = Object()

suspend fun doSomething(i: Int) {
    
    
    synchronized(LOCK) {
    
    
        println("#$i enter critical section.")

        // do something
        delay(1000) // <- The 'delay' suspension point is inside a critical section

        println("#$i exit critical section.")
    }
}

fun main() = runBlocking {
    
    
    repeat(2) {
    
     i ->
        launch(Dispatchers.Default) {
    
    
            println("#$i thread name: ${
      
      Thread.currentThread().name}")
            doSomething(i)
        }
    }
}

次のコンパイルエラーが表示されます。

"The 'delay' suspension point is inside a critical section" 

2.日常の同期にはMutexが必要です


オンライン実験は、それSynchronizedが通常の同期のシーンでは使用できないことを証明しており、通常の同期を使用する必要がありますMutex

val mutex = Mutex()

suspend fun doSomething(i: Int) {
    
    
    mutex.withLock {
    
    
        println("#$i enter critical section.")

        // do something
        delay(1000) // <- The 'delay' suspension point is inside a critical section

        println("#$i exit critical section.")
    }
}

fun main() = runBlocking {
    
    
    repeat(2) {
    
     i ->
        launch(Dispatchers.Default) {
    
    
            println("#$i thread name: ${
      
      Thread.currentThread().name}")
            doSomething(i)
        }
    }
}
#0 thread name: DefaultDispatcher-worker-1
#1 thread name: DefaultDispatcher-worker-2
#1 enter critical section.
#1 exit critical section.
#0 enter critical section.
#0 exit critical section.

3.サスペンド機能の性質


Synchronoizedが無効なのはなぜですか?これには、サスペンド機能の実装から答えを見つける必要があります

一般的なフロントエンド通信シナリオを想像してみてください。

  1. リモートアクセスtoken
  2. トークンに基づいて作成post
  3. クライアントディスプレイ

通常の書き込みでは、CPS(継続受け渡しスタイル)を使用する必要があります。これは、単にコールバックです。

class Item()
class Post()

//1 .获取token
fun requestToken(callback: (String) -> Unit) {
    
    
    // ... remote service
    callback("token")
}

//2. 创建post
fun createPost(token: String, item: Item, callback: (Post) -> Unit) {
    
    
    // ... remote service
    callback(Post())
}
//3. 显示
fun processPost(post: Post) {
    
    
    // do post
}

fun postItem(item: Item) {
    
    
    requestToken {
    
     token ->
        createPost(token, item) {
    
     post ->
            processPost(post)
        }
    }
}

サスペンド機能を使用して同じロジックを実装する場合:

class Item()
class Post()

suspend fun requestToken(): String {
    
    
    // get token from api
    return "token"
}

suspend fun createPost(token: String, item: Item): Post {
    
    
    // create post
    return Post()
}

fun processPost(post: Post) {
    
    
    // do post
}

suspend fun postItem(item: Item) {
    
    
    val token = requestToken()
    val post = createPost(token, item)
    processPost(post)
}

サスペンド関数を使用すると、CPSによってもたらされたテンプレートコードを取り除くことができますが、その本質はCPSの構文上の砂糖にすぎません。デコンパイル後のサスペンド関数は、関数を完了するためのコールバックに依存します。

// kotlin
suspend fun createPost(token: String, item: Item): Post {
    
     ... }

// Java/JVM
Object createPost(String token, Item item, Continuation<Post> cont) {
    
     ... }

Continuation実際にはコールバックです

interface Continuation<in T> {
    
    
    val context: CoroutineContext
    fun resume(value: T)
    fun resumeWithException(exception: Throwable)
}

上記の例postItemの一連のサスペンド関数呼び出しは、コンパイル解除後の複数のコールバックのネスト同等ですが、コルーチンは、ネストを回避するためにラベル+再帰呼び出しを使用します。

suspend fun postItem(item: Item, label: Int) {
    
    
    switch (label) {
    
    
        case 0:
            val token = requestToken()
        case 1:
            val post = createPost(token, item)
        case 2:
            processPost(post)
    }
}

この一連の呼び出しはステートフルであるため、定義ThisSMは現在の状態を保存しインターフェイスThisSM実装Continuationresumeます。独自の状態を更新した後、再開フローを介して次の処理ステージに渡され、いわゆるステートマシンモデルが実装されます。

fun postItem(item: Item, cont: Continuation) {
    
    
    val sm = cont as? ThisSM ?: object : ThisSM {
    
     
        val initialCont = cont
        fun resume() {
    
    
            postIem(null, this)
        }
     }
    switch (sm.label) {
    
    
        case 0:
            sm.item = item
            sm.label = 1
            requestToken(sm)
        case 1:
            val item = sm.item
            val token = sm.result as String
            sm.label = 2
            createPost(token, item, sm)
        case 2:
            processPost(post)
            sm.initialCont.reusme()
    }
}

より多くのサスペンド機能、参照は復号化される可能性があります修飾子Kotlinサスペンドコルーチン


4.同期が無効な理由


とはいえ、Synchronizedがサスペンド機能に対して無効なのはなぜですか?

最初の例を見てください

@Synchronized
suspend fun doSomething(i: Int) {
    
    
    println("#$i enter critical section.")

    // do something
    delay(1000)

    println("#$i exit critical section.")
}

コンパイル解除後はこんな感じ

@Synchronized
fun doSomething(i: Int, cont: Continuation) {
    
    
    val sm = cont as? ThisSM ?: ThisSM {
    
     ... }
    switch (sm.label) {
    
    
        case 0:
            println("#$i enter critical section.")

            sm.label = 1
            delay(1000, sm)
        case 1:
            println("#$i exit critical section.")
    }
}

delay呼び出し後、doSomething関数は終了してSynchronized無効になり、遅延も非同期呼び出しであり、後続のdoSomethingはロックの影響を受けなくなります。したがって、ログにシリアルのみthread nameenter保持し、パラレル出力enterありexit続けます

参照

Kotlincoroutineのサスペンド修飾子を復号化します

おすすめ

転載: blog.csdn.net/vitaviva/article/details/108929094