Concurrencia y multihilo en Kotlin: de la teoría a la práctica
Las aplicaciones modernas requieren un funcionamiento rápido y receptivo. Ya sea la carga de datos desde la red, el procesamiento de grandes conjuntos de datos o el trabajo con la interfaz de usuario, no se puede prescindir de la concurrencia y el multihilo. Kotlin, como lenguaje moderno para JVM, Android y proyectos multiplataforma, ofrece herramientas flexibles para resolver estas tareas. En este artículo, analizaremos los enfoques clave: desde los hilos clásicos hasta las corrutinas ligeras, y mostraremos cómo escribir código eficiente.
1. Hilos (Threads) en Kotlin: el enfoque clásico
Los hilos son el mecanismo básico de ejecución paralela, heredado de Java. En Kotlin, puedes crear hilos igual que en Java, pero con una sintaxis más concisa.
Ejemplo simple de inicio de un hilo:
fun main() { thread { println("Hilo iniciado: ${Thread.currentThread().name}") Thread.sleep(1000) println("Hilo finalizado") } println("Hilo principal: ${Thread.currentThread().name}") Thread.sleep(2000)}Sin embargo, los hilos tienen desventajas: son pesados (cada hilo consume alrededor de 1 MB de memoria), su cantidad es limitada y el cambio de contexto es costoso. Para aplicaciones modernas con miles de tareas paralelas, los hilos no son la mejor opción. Aquí es donde las corrutinas vienen al rescate.
2. Corrutinas (Coroutines): una alternativa ligera
Las corrutinas son la característica principal de Kotlin para la concurrencia. Funcionan sobre los hilos, pero utilizan menos recursos. Un solo hilo puede ejecutar miles de corrutinas, suspendiéndolas y reanudándolas sin bloqueo.
Para empezar, añade la dependencia en build.gradle.kts:
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")}Ejemplo de una corrutina simple:
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) // retardo no bloqueante println("¡Mundo desde la corrutina!") } println("Hola desde la corrutina principal") delay(2000L)}Aquí, launch inicia una nueva corrutina, y delay la suspende sin bloquear el hilo. runBlocking se utiliza para bloquear el hilo principal hasta que todas las corrutinas finalicen (normalmente se usa solo en main y pruebas).
2.1. Funciones suspend
La palabra clave suspend permite suspender la ejecución de una función sin bloquear el hilo. Esta es la base de la programación asíncrona en Kotlin.
suspend fun fetchUser(): String { delay(500L) // simulación de una solicitud de red return "User123"}
fun main() = runBlocking { val user = fetchUser() println("Usuario obtenido: $user")}2.2. Async y Deferred para tareas paralelas
Si necesitas ejecutar varias tareas en paralelo y esperar sus resultados, usa async y Deferred:
fun main() = runBlocking { val time = measureTimeMillis { val user = async { fetchUser() } val profile = async { fetchProfile() } println("Resultados: ${user.await()}, ${profile.await()}") } println("Tiempo de ejecución: $time ms")}
suspend fun fetchUser(): String { delay(500) return "User"}
suspend fun fetchProfile(): String { delay(500) return "Profile"}Ambas tareas se ejecutan en paralelo, lo que reduce el tiempo total.
3. Dispachadores de corrutinas (Dispatchers)
Los dispachadores determinan en qué hilos se ejecutarán las corrutinas. Tipos principales:
Dispatchers.Default— para tareas intensivas en CPU (por defecto).Dispatchers.IO— para operaciones de entrada/salida (red, archivos).Dispatchers.Main— para el hilo de UI (Android, JavaFX).