[Kotlin Coroutine] Evite usar el modificador @Synchronized en la función de suspensión

Inserte la descripción de la imagen aquí
Cuando usamos Kotlin, usualmente usamos @Synchronizedsincronización entre subprocesos, por lo tanto, muchos estudiantes que son nuevos en las corrutinas agregan la vista a la función suspender @Synchronizedpara lograr "sincronización entre corrutinas" ¿Es esto efectivo?

1. 协 程 + Sincronizado?


Normalmente, las corrutinas pueden ayudarnos a realizar tareas paralelas:

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)
        }
    }
}

Se puede ver en el registro que la entersuma de las dos tareas se exitgenera en paralelo y no hay orden

#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.

A continuación, agregue un @Synchronizedintento:

@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.

Para las funciones ordinarias, debido a la adición de Synchronized, los dos subprocesos deben ejecutarse secuencialmente, pero el registro anterior muestra que para las funciones suspendidas, independientemente de si se agrega Synchronized o no, todavía se ejecutan en paralelo ( enter, exitsalida simultánea).

Cambiemos la forma de escribir, intente agregar Synchronized dentro de la función de suspensión:

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)
        }
    }
}

Aparece el siguiente error de compilación:

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

2. Se requiere mutex para la sincronización de rutina


El experimento en línea demuestra que no Synchronizedse puede usar en la escena de la sincronización de corrutina, y se debe usar la sincronización de corrutinaMutex

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. La naturaleza de la función de suspensión


¿Por qué Synchronoized no es válido? Esto requiere encontrar la respuesta a partir de la implementación de la función de suspensión.

Imagine un escenario de comunicación de front-end común:

  1. Acceso remototoken
  2. Crear basado en tokenpost
  3. Pantalla del cliente

La escritura ordinaria debe usar CPS(estilo de paso de continuación), que es simplemente devolución de llamada

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)
        }
    }
}

Si usa la función de suspensión para implementar la misma lógica:

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)
}

La función de suspensión nos permite deshacernos del código de plantilla traído por CPS, pero su esencia es solo el azúcar sintáctico de CPS. La función de suspensión después de la descompilación aún depende de la devolución de llamada para completar la función:

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

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

ContinuationEs en realidad una devolución de llamada

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

postItemLa serie de llamadas a la función de suspensión en el ejemplo anterior es equivalente al anidamiento de múltiples devoluciones de llamada después de la descompilación, pero la corrutina usa etiquetas + llamadas recursivas para evitar el anidamiento:

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)
    }
}

Esta serie de llamadas tiene estado, por lo que la definición ThisSMguarda el estado actual, ThisSMimplementa Continuationla resumeinterfaz y actualiza su propio estado a través del flujo de reanudación a la siguiente etapa de procesamiento para implementar el llamado modelo de máquina de estado.

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()
    }
}

Más función de suspensión, la referencia se puede descifrar modificador Kotlin suspender coroutine


4. Por qué Synchronoized no es válido


Habiendo dicho tanto, ¿por qué Synchronized no es válido para la función de suspensión?

Mira el ejemplo al principio.

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

    // do something
    delay(1000)

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

Se ve así después de la descompilación

@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.")
    }
}

delayDespués de la llamada, la doSomethingfunción sale y se vuelve Synchronizedinválida, y el retraso también es una llamada asincrónica, y el doSomething posterior ya no se ve afectado por el bloqueo. Tan solo thread namey entermantuvo una serie en el registro, entery exitcontinúa siendo una salida paralela

referencia

Descifrar el modificador de suspensión de la co-rutina de Kotlin

Supongo que te gusta

Origin blog.csdn.net/vitaviva/article/details/108929094
Recomendado
Clasificación