Middle+
Hoe kan synchronisatie in Kotlin worden geïmplementeerd met behulp van coroutines?
sobes.tech AI
Antwoord van AI
Synchronisatie in Kotlin-coroutines wordt bereikt met behulp van speciale primitieve en datastructuren uit de bibliotheek kotlinx.coroutines.
Belangrijkste benaderingen:
-
Mutex
- Biedt onderlinge uitsluiting om kritieke code-secties te beschermen.
- De coroutine die probeert een Mutex te verkrijgen, wordt geblokkeerd totdat deze wordt vrijgegeven.
- Beschikt over functies
lock()enunlock(), evenalswithLockdie meer idiomatisch is.
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) // Verwacht 10000 } -
Semaphore
- Beperkt het aantal coroutines dat gelijktijdig toegang kan krijgen tot een bron of een bepaald codeblok kan uitvoeren.
- Beheert een permissie-pool.
- Beschikt over functies
acquire()enrelease(), evenalswithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Maximaal 2 coroutines tegelijk suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Coroutine $id heeft een permit verkregen. Werkt...") delay(100) // Simulatie van werk println("Coroutine $id heeft permit vrijgegeven.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
Atomische operaties (
kotlinx.coroutines.atomicvan)- Bieden thread-veilige operaties op primitieve types en referenties.
- Gebruiken CPU-laag instructies (CAS - Compare-and-Swap).
- Geschikt voor eenvoudige operaties zonder expliciete blokkades.
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) // Verwacht 10000 } -
Gedeelde datastructuren op een single-thread dispatcher
- De eenvoudigste aanpak. Corrutines uitvoeren op een enkele thread-omgeving (
Dispatchers.Default.limitedParallelism(1)ofnewSingleThreadContext) garandeert sequentiële uitvoering van de code, waardoor racecondities worden voorkomen. Het is geen expliciete synchronisatie-primitief, maar zorgt voor synchronisatie door toegang te serialiseren.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Veilig, omdat het op één thread wordt uitgevoerd } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Verwacht 10000 } - De eenvoudigste aanpak. Corrutines uitvoeren op een enkele thread-omgeving (
-
Kanalen
- Zijn geen primitieve synchronisatie-instrumenten op zich, maar kunnen worden gebruikt voor veilige gegevensoverdracht tussen coroutines, wat indirect problemen met toegangssynchronisatie oplost.
- Bieden een manier om datastromen van de ene naar de andere coroutine over te dragen.
De keuze van aanpak hangt af van het gebruiksscenario. Mutex en Semaphore bieden klassieke blokkeringmechanismen, atomische operaties zijn efficiënt voor eenvoudige atomaire updates, en de single-thread dispatcher is handig wanneer toegang tot gedeelde gegevens strikt sequentieel moet zijn. Kanalen worden gebruikt voor communicatie en coördinatie, niet voor directe bescherming van gedeelde bronnen.