Concurrence et multithreading en Kotlin : de la théorie à la pratique
Les applications modernes nécessitent un fonctionnement rapide et réactif. Qu'il s'agisse de charger des données depuis le réseau, de traiter de grands tableaux ou de travailler avec une interface utilisateur, la concurrence et le multithreading sont indispensables. Kotlin, en tant que langage moderne pour la JVM, Android et les projets multiplateformes, offre des outils flexibles pour résoudre ces tâches. Dans cet article, nous allons examiner les approches clés : des threads classiques aux coroutines légères, et montrer comment écrire un code efficace.
1. Threads en Kotlin : l'approche classique
Les threads sont le mécanisme de base de l'exécution parallèle, hérité de Java. En Kotlin, vous pouvez créer des threads de la même manière qu'en Java, mais avec une syntaxe plus concise.
Exemple simple de lancement d'un thread :
fun main() { thread { println("Thread lancé : ${Thread.currentThread().name}") Thread.sleep(1000) println("Thread terminé") } println("Thread principal : ${Thread.currentThread().name}") Thread.sleep(2000)}Cependant, les threads ont des inconvénients : ils sont lourds (chaque thread consomme environ 1 Mo de mémoire), leur nombre est limité et le changement de contexte est coûteux. Pour les applications modernes avec des milliers de tâches parallèles, les threads ne sont pas le meilleur choix. C'est là que les coroutines viennent à la rescousse.
2. Coroutines : une alternative légère
Les coroutines sont la principale fonctionnalité de Kotlin pour la concurrence. Elles fonctionnent au-dessus des threads, mais utilisent moins de ressources. Un seul thread peut exécuter des milliers de coroutines, en les suspendant et en les reprenant sans blocage.
Pour commencer, ajoutez la dépendance dans build.gradle.kts :
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")}Exemple d'une coroutine simple :
import kotlinx.coroutines.*
fun main() = runBlocking { launch { delay(1000L) // délai non bloquant println("Monde depuis la coroutine !") } println("Bonjour depuis la coroutine principale") delay(2000L)}Ici, launch lance une nouvelle coroutine, et delay la suspend sans bloquer le thread. runBlocking est utilisé pour bloquer le thread principal jusqu'à la fin de toutes les coroutines (généralement utilisé uniquement dans main et les tests).
2.1. Fonctions suspend
Le mot-clé suspend permet de suspendre l'exécution d'une fonction sans bloquer le thread. C'est la base de la programmation asynchrone en Kotlin.
suspend fun fetchUser(): String { delay(500L) // simulation d'une requête réseau return "User123"}
fun main() = runBlocking { val user = fetchUser() println("Utilisateur reçu : $user")}2.2. Async et Deferred pour les tâches parallèles
Si vous devez exécuter plusieurs tâches en parallèle et attendre leurs résultats, utilisez async et Deferred :
fun main() = runBlocking { val time = measureTimeMillis { val user = async { fetchUser() } val profile = async { fetchProfile() } println("Résultats : ${user.await()}, ${profile.await()}") } println("Temps d'exécution : $time ms")}
suspend fun fetchUser(): String { delay(500) return "User"}
suspend fun fetchProfile(): String { delay(500) return "Profile"}Les deux tâches s'exécutent en parallèle, ce qui réduit le temps total.
3. Dispatchers de coroutines
Les dispatchers déterminent sur quels threads les coroutines seront exécutées. Les principaux types :
Dispatchers.Default— pour les tâches intensives en CPU (par défaut).Dispatchers.IO— pour les opérations d'entrée/sortie (réseau, fichiers).Dispatchers.Main— pour le thread UI (Android, JavaFX).