Concurrencia y multihilo en Kotlin: nivel avanzado
\nEn el desarrollo moderno con Kotlin, especialmente para aplicaciones de servidor y Android, una gestión adecuada de la concurrencia es clave para el rendimiento y la capacidad de respuesta. A nivel avanzado, no basta con simplemente lanzar una corrutina. Es necesario comprender cómo sincronizar eficazmente el acceso a los datos, gestionar los hilos y evitar los bloqueos "infernales". En este artículo, analizaremos técnicas avanzadas: desde los detalles de kotlinx.coroutines hasta colecciones thread-safe y canales.
1. Inmersión profunda en corrutinas: Structured Concurrency y Scopes
\nLa base del trabajo avanzado es la Structured Concurrency. Este es un principio por el cual el ciclo de vida de una corrutina está vinculado a un ámbito (scope). Si la corrutina padre se cancela, todas sus tareas hijas finalizan automáticamente. Esto evita fugas de memoria y procesos incontrolados.
\nA nivel avanzado, debe crear sus propios scopes, en lugar de depender del GlobalScope global.
import kotlinx.coroutines.*\nimport kotlin.coroutines.CoroutineContext\n\nclass DataRepository {\n // Scope propio con dispatcher y manejador de excepciones\n private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { _, throwable ->\n println("Error no manejado: ${throwable.message}")\n })\n\n fun loadData() {\n scope.launch {\n // corrutinas hijas\n val data = async { fetchFromNetwork() }\n val cache = async { loadFromCache() }\n process(data.await(), cache.await())\n }\n }\n\n fun cleanup() {\n scope.cancel() // cancela todas las corrutinas en el scope\n }\n}\n\nUtilice SupervisorJob cuando el fallo de una corrutina hija no deba cancelar a las demás. Esto es crítico para capas de UI o solicitudes paralelas independientes.
2. Colecciones Thread-Safe y operaciones atómicas
\nIncluso con corrutinas, si trabaja con datos mutables compartidos, necesita sincronización. Kotlin ofrece varios enfoques:
\n- \n
- Tipos atómicos (
AtomicInteger,AtomicReference) — para contadores y banderas simples. \n - Colecciones Thread-Safe (
ConcurrentHashMap,CopyOnWriteArrayList) — para estructuras complejas. \n - Mutex de
kotlinx.coroutines— para secciones críticas dentro de corrutinas. \n
import kotlinx.coroutines.*\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.atomic.AtomicInteger\n\nclass SafeCounter {\n private val mutex = Mutex()\n private var counter = 0\n\n suspend fun increment() {\n mutex.withLock {\n counter++ // sección crítica\n }\n }\n\n fun getCounter(): Int = counter\n}\n\nclass CacheManager {\n private val cache = ConcurrentHashMap<String, String>()\n private val hitCount = AtomicInteger(0)\n\n fun put(key: String, value: String) {\n cache[key] = value\n }\n\n fun get(key: String): String? {\n val result = cache[key]\n if (result != null) hitCount.incrementAndGet()\n return result\n }\n}\n\nImportante: Mutex es una función suspend, por lo que no bloquea el hilo, sino que suspende la corrutina. Esto es más eficiente que synchronized.
3. Canales (Channels) y Flow: flujos de datos reactivos
\nPara transferir datos entre corrutinas, utilice Channels (análogo a BlockingQueue para corrutinas) y Flow (flujo asíncrono frío).
Canales: Productor-Consumidor
\nimport kotlinx.coroutines.channels.*\nimport kotlinx.coroutines.*\n\nfun Corout