Sobes.tech
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:

  1. Mutex

    • Zapewnia wzajemne wykluczanie do ochrony sekcji krytycznych kodu.
    • Korutyna próbująca uzyskać Mutex zostanie zablokowana do czasu jego zwolnienia.
    • Posiada funkcje lock() i unlock(), 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
    }
    
  2. 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() i release(), a także withPermit.
    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)
            }
        }
    }
    
  3. **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
    }
    
  4. Wspólne struktury danych na pojedynczym dispatcherze wątku

    • Najprostsze podejście. Uruchomienie korutyn na kontekście jednego wątku (Dispatchers.Default.limitedParallelism(1) lub newSingleThreadContext) 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
    }
    
  5. 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.