Sobes.tech
Middle+

Ako možno implementovať synchronizáciu v Kotlin pomocou coroutines?

sobes.tech AI

Odpoveď od AI

Synchronizácia v Kotlin korutinách sa dosahuje pomocou špeciálnych primitívov a dátových štruktúr z knižnice kotlinx.coroutines.

Hlavné prístupy:

  1. Mutex

    • Poskytuje vzájomné vylúčenie na ochranu kritických sekcií kódu.
    • Korutina, ktorá sa pokúsi získať Mutex, sa zablokuje, kým nebude uvoľnený.
    • Má funkcie lock() a unlock(), ako aj idiomatický 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) // Očakávame 10000
    }
    
  2. Semaphore

    • Obmedzuje počet korutín, ktoré môžu súčasne získať prístup ku zdroju alebo vykonať určitý blok kódu.
    • Riadi skupinu povolení.
    • Má funkcie acquire() a release(), ako aj withPermit.
    import kotlinx.coroutines.sync.Semaphore
    import kotlinx.coroutines.sync.withPermit
    import kotlinx.coroutines.*
    
    val semaphore = Semaphore(2) // Súčasne môžu pracovať 2 korutiny
    
    suspend fun doLimitedWork(id: Int) {
        semaphore.withPermit {
            println("Korutina $id získala povolenie. Pracuje...")
            delay(100) // Imitácia práce
            println("Korutina $id uvoľnila povolenie.")
        }
    }
    
    suspend fun main() = coroutineScope {
        repeat(5) { i ->
            launch {
                doLimitedWork(i)
            }
        }
    }
    
  3. Atómové operácie (z kotlinx.coroutines.atomic)

    • Poskytujú bezpečné operácie nad primitívnymi typmi a referenciami.
    • Používajú nízkoúrovňové CPU inštrukcie (CAS - Compare-and-Swap).
    • Vhodné pre jednoduché operácie bez explicitných zámkov.
    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) // Očakávame 10000
    }
    
  4. Zdieľané dátové štruktúry na dispatcheri s jedným vláknom

    • Najjednoduchší prístup. Spustenie korutín na kontexte s jedným vláknom (Dispatchers.Default.limitedParallelism(1) alebo newSingleThreadContext) garantuje sekvenčné vykonávanie kódu, čím sa vylúčia závody dát. Nie je to explicitný primitív na synchronizáciu, ale zabezpečuje synchronizáciu prostredníctvom serializácie prístupu.
    import kotlinx.coroutines.*
    
    val mySingleThreadContext = newSingleThreadContext("SingleThread")
    var sharedData = mutableListOf<Int>()
    
    suspend fun addToSharedData(value: Int) {
        sharedData.add(value) // Bezpečné, pretože sa vykonáva na jednom vlákne
    }
    
    suspend fun main() = withContext(mySingleThreadContext) {
        repeat(10_000) {
            launch {
                 addToSharedData(it)
            }
        }
        println(sharedData.size) // Očakávame 10000
    }
    
  5. Kanály

    • Nie sú primitívmi na synchronizáciu v čistej podobe, ale môžu byť použité na bezpečný prenos dát medzi korutinami, čím nepriamo riešia problémy synchronizácie prístupu k prenášaným dátam.
    • Poskytujú spôsob prenosu toku dát z jednej korutiny do druhej.

Výber prístupu závisí od scenára použitia. Mutex a Semaphore poskytujú klasické mechanizmy blokovania, atomické operácie sú efektívne pre jednoduché atomické aktualizácie, a dispatcher s jedným vláknom je vhodný, keď má byť prístup k spoločným dátam prísne sekvenčný. Kanály sa používajú na komunikáciu a koordináciu, nie na priame zabezpečenie spoločných zdrojov.