Cuando usamos Kotlin, usualmente usamos @Synchronized
sincronización entre subprocesos, por lo tanto, muchos estudiantes que son nuevos en las corrutinas agregan la vista a la función suspender @Synchronized
para 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 enter
suma de las dos tareas se exit
genera 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 @Synchronized
intento:
@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
, exit
salida 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 Synchronized
se 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:
- Acceso remoto
token
- Crear basado en token
post
- 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) {
... }
Continuation
Es en realidad una devolución de llamada
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
postItem
La 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 ThisSM
guarda el estado actual, ThisSM
implementa Continuation
la resume
interfaz 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.")
}
}
delay
Después de la llamada, la doSomething
función sale y se vuelve Synchronized
invá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 name
y enter
mantuvo una serie en el registro, enter
y exit
continúa siendo una salida paralela
referencia
Descifrar el modificador de suspensión de la co-rutina de Kotlin