Middle+
¿Cómo se puede implementar la sincronización en Kotlin usando corutinas?
sobes.tech AI
Respuesta de la IA
La sincronización en las corrutinas de Kotlin se logra mediante primitivas y estructuras de datos especiales de la biblioteca kotlinx.coroutines.
Enfoques principales:
-
Mutex
- Proporciona exclusión mutua para proteger secciones críticas del código.
- La corrutina que intenta obtener un Mutex se bloquea hasta que se libera.
- Tiene funciones
lock()yunlock(), así comowithLockmás idiomático.
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.* val mutex = Mutex() var counter = 0 suspend fun increment() { mutex.withLock { counter++ } } suspend fun main() = coroutineScope { repeat(10_000) { launch { increment() } } println(counter) // Se espera 10000 } -
Semaphore
- Limita la cantidad de corrutinas que pueden acceder a un recurso o ejecutar un bloque de código simultáneamente.
- Gestiona un grupo de permisos.
- Tiene funciones
acquire()yrelease(), así comowithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Hasta 2 corrutinas simultáneamente suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Corrutina $id adquirió un permiso. Trabajando...") delay(100) // Simulación de trabajo println("Corrutina $id liberó un permiso.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
**Operaciones atómicas (de
kotlinx.coroutines.atomic)- Ofrecen operaciones seguras para hilos sobre tipos primitivos y referencias.
- Utilizan instrucciones de CPU de bajo nivel (CAS - Compare-and-Swap).
- Adecuadas para operaciones simples sin bloqueos explícitos.
import kotlinx.coroutines.atomic.AtomicInt import kotlinx.coroutines.* val atomicCounter = AtomicInt(0) suspend fun atomicIncrement() { atomicCounter.incrementAndGet() } suspend fun main() = coroutineScope { repeat(10_000) { launch { atomicIncrement() } } println(atomicCounter.value) // Se espera 10000 } -
Estructuras de datos compartidas en un despachador de un solo hilo
- El enfoque más simple. Ejecutar corrutinas en un contexto de un solo hilo (
Dispatchers.Default.limitedParallelism(1)onewSingleThreadContext) garantiza la ejecución secuencial del código, evitando condiciones de carrera. No es una primitiva de sincronización explícita, pero asegura sincronización mediante serialización del acceso.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Seguro, ya que se ejecuta en un solo hilo } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Se espera 10000 } - El enfoque más simple. Ejecutar corrutinas en un contexto de un solo hilo (
-
Canales
- No son primitivas de sincronización en sí mismas, pero pueden usarse para transferir datos de forma segura entre corrutinas, lo que resuelve indirectamente problemas de sincronización de acceso a los datos transmitidos.
- Proporcionan un método para transferir un flujo de datos de una corrutina a otra.
La elección del enfoque depende del escenario de uso. Mutex y Semaphore ofrecen mecanismos clásicos de bloqueo, las operaciones atómicas son eficientes para actualizaciones simples, y el despachador de un solo hilo es conveniente cuando el acceso a datos compartidos debe ser estrictamente secuencial. Los canales se usan para comunicación y coordinación, no para protección directa de recursos compartidos.