Middle+
Wie kann man die Synchronisation in Kotlin mit Coroutinen implementieren?
sobes.tech KI
Antwort von AI
Die Synchronisierung in Kotlin-Coroutines wird mithilfe spezieller Primitive und Datenstrukturen aus der Bibliothek kotlinx.coroutines erreicht.
Hauptansätze:
-
Mutex
- Bietet gegenseitigen Ausschluss zum Schutz kritischer Codeabschnitte.
- Die Coroutine, die versucht, ein Mutex zu erhalten, wird blockiert, bis es freigegeben wird.
- Hat die Funktionen
lock()undunlock(), sowiewithLockals idiomatischere Variante.
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) // Erwartet 10000 } -
Semaphore
- Begrenzung der Anzahl gleichzeitiger Koroutinen, die auf eine Ressource zugreifen oder einen bestimmten Codeblock ausführen können.
- Verwalten eines Permissons-Pools.
- Hat die Funktionen
acquire()undrelease(), sowiewithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Maximal 2 Koroutinen gleichzeitig suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Koroutine $id hat eine Erlaubnis erhalten. Arbeitet...") delay(100) // Arbeit simulieren println("Koroutine $id hat Erlaubnis freigegeben.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
**Atomare Operationen (aus
kotlinx.coroutines.atomic)- Bieten thread-sichere Operationen auf primitiven Typen und Referenzen.
- Verwenden CPU-niedrigstufige Anweisungen (CAS - Compare-and-Swap).
- Geeignet für einfache Operationen ohne explizite Sperren.
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) // Erwartet 10000 } -
Gemeinsame Datenstrukturen auf einem Single-Thread-Dispatcher
- Der einfachste Ansatz. Ausführen von Koroutinen in einem Single-Thread-Kontext (
Dispatchers.Default.limitedParallelism(1)odernewSingleThreadContext) garantiert sequentielle Ausführung, vermeidet Race Conditions. Keine explizite Synchronisationsprimitive, aber Serialisierung des Zugriffs.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Sicher, da auf einem einzigen Thread } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Erwartet 10000 } - Der einfachste Ansatz. Ausführen von Koroutinen in einem Single-Thread-Kontext (
-
Kanäle
- Sind keine Synchronisationsprimitive an sich, können aber zum sicheren Datenaustausch zwischen Koroutinen verwendet werden, was indirekt Synchronisationsprobleme beim Zugriff auf die übertragenen Daten löst.
- Bieten eine Möglichkeit, Datenströme von einer Koroutine zur anderen zu übertragen.
Die Wahl des Ansatzes hängt vom Anwendungsfall ab. Mutex und Semaphore bieten klassische Sperrmechanismen, atomare Operationen sind effizient für einfache atomare Updates, und der Single-Thread-Dispatcher ist praktisch, wenn der Zugriff auf gemeinsame Daten strikt sequentiell erfolgen soll. Kanäle werden für Kommunikation und Koordination genutzt, nicht für den direkten Schutz gemeinsamer Ressourcen.