Middle+
Jak można zaimplementować synchronizację w Kotlinie z użyciem korutyn?
sobes.tech AI
Odpowiedź od AI
Synchronizacja w korutynach Kotlin osiągana jest za pomocą specjalnych primitive i struktur danych z biblioteki kotlinx.coroutines.
Główne podejścia:
-
Mutex
- Zapewnia wzajemne wykluczanie do ochrony sekcji krytycznych kodu.
- Korutyna próbująca uzyskać Mutex zostanie zablokowana do czasu jego zwolnienia.
- Posiada funkcje
lock()iunlock(), a także bardziej idiomatycznąwithLock.
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) // Oczekujemy 10000 } -
Semaphore
- Ogranicza liczbę korutyn, które mogą jednocześnie uzyskać dostęp do zasobu lub wykonać określony blok kodu.
- Zarządza pulą zezwoleń.
- Posiada funkcje
acquire()irelease(), a takżewithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Jednocześnie może działać 2 korutyny suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Korutyna $id uzyskała permit. Pracuje...") delay(100) // Symulacja pracy println("Korutyna $id zwolniła permit.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
**Operacje atomowe (z
kotlinx.coroutines.atomic)- Zapewniają bezpieczne dla wątków operacje na typach prymitywnych i odwołaniach.
- Używają niskopoziomowych instrukcji CPU (CAS - Compare-and-Swap).
- Nadają się do prostych operacji bez jawnych blokad.
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) // Oczekujemy 10000 } -
Wspólne struktury danych na pojedynczym dispatcherze wątku
- Najprostsze podejście. Uruchomienie korutyn na kontekście jednego wątku (
Dispatchers.Default.limitedParallelism(1)lubnewSingleThreadContext) gwarantuje sekwencyjne wykonanie kodu, eliminując wyścigi danych. Nie jest to jawna prymitywna synchronizacji, ale zapewnia synchronizację poprzez serializację dostępu.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Bezpiecznie, ponieważ wykonywane na jednym wątku } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Oczekujemy 10000 } - Najprostsze podejście. Uruchomienie korutyn na kontekście jednego wątku (
-
Kanały
- Nie są primitiveami synchronizacji same w sobie, ale mogą być używane do bezpiecznego przesyłania danych między korutynami, co pośrednio rozwiązuje problemy synchronizacji dostępu do przesyłanych danych.
- Zapewniają sposób przesyłania strumienia danych z jednej korutyny do drugiej.
Wybór podejścia zależy od scenariusza użycia. Mutex i Semaphore zapewniają klasyczne mechanizmy blokowania, operacje atomowe są wydajne dla prostych aktualizacji, a pojedynczy dispatcher wątku jest wygodny, gdy dostęp do wspólnych danych musi być ściśle sekwencyjny. Kanały służą do komunikacji i koordynacji, a nie do bezpośredniej ochrony wspólnych zasobów.