Concurrence et multithreading en Kotlin : niveau avancé
\nDans le développement moderne avec Kotlin, en particulier pour les applications serveur et Android, une gestion avancée de la concurrence est la clé de la performance et de la réactivité. Au niveau avancé, il ne suffit pas de lancer une coroutine. Il faut comprendre comment synchroniser efficacement l'accès aux données, gérer les threads et éviter les blocages infernaux. Dans cet article, nous analyserons les techniques avancées : des subtilités de kotlinx.coroutines aux collections thread-safe et aux canaux.
1. Plongée approfondie dans les coroutines : Structured Concurrency et Scopes
\nLa base du travail avancé est la Structured Concurrency. C'est un principe selon lequel le cycle de vie d'une coroutine est lié à une portée (scope). Si la coroutine parente est annulée, toutes ses tâches filles se terminent automatiquement. Cela évite les fuites mémoire et les processus incontrôlés.
\nAu niveau avancé, vous devez créer vos propres scopes, plutôt que de vous fier au GlobalScope global.
import kotlinx.coroutines.*\nimport kotlin.coroutines.CoroutineContext\n\nclass DataRepository {\n // Scope personnalisé avec dispatcher et gestionnaire d'exceptions\n private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler { _, throwable ->\n println("Erreur non gérée : ${throwable.message}")\n })\n\n fun loadData() {\n scope.launch {\n // coroutines filles\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() // annule toutes les coroutines du scope\n }\n}\n\nUtilisez SupervisorJob lorsque l'échec d'une coroutine fille ne doit pas annuler les autres. C'est crucial pour les couches UI ou les requêtes parallèles indépendantes.
2. Collections thread-safe et opérations atomiques
\nMême avec les coroutines, si vous travaillez avec des données mutables partagées, une synchronisation est nécessaire. Kotlin propose plusieurs approches :
\n- \n
- Types atomiques (
AtomicInteger,AtomicReference) — pour les compteurs et drapeaux simples. \n - Collections thread-safe (
ConcurrentHashMap,CopyOnWriteArrayList) — pour les structures complexes. \n - Mutex de
kotlinx.coroutines— pour les sections critiques à l'intérieur des coroutines. \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++ // section critique\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\nImportant : Mutex est une fonction suspend, donc il ne bloque pas le thread, mais suspend la coroutine. C'est plus efficace que synchronized.
3. Canaux (Channels) et Flow : flux de données réactifs
\nPour transférer des données entre coroutines, utilisez les Channels (équivalent de BlockingQueue pour les coroutines) et Flow (flux asynchrone froid).
Canaux : Producteur-Consommateur
\nimport kotlinx.coroutines.channels.*\nimport kotlinx.coroutines.*\n\nfun Corout