Sobes.tech
Middle+

Hogyan valósítható meg a szinkronizáció Kotlinben coroutine-ok használatával?

sobes.tech MI

Válasz az MI-től

A Kotlin coroutine-ok szinkronizálása speciális primitívek és adatszerkezetek segítségével történik a kotlinx.coroutines könyvtárból.

Fő megközelítések:

  1. Mutex

    • Kölcsönös kizárást biztosít a kritikus szekciók védelmére.
    • Egy coroutine, amely megpróbálja megszerezni a Mutexet, blokkolódik, amíg az el nem szabadul.
    • lock() és unlock() funkciókkal, valamint a withLock idiomatikusabb változatával rendelkezik.
    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) // Várható eredmény 10000
    }
    
  2. Semaphore

    • Korlátozza az egyszerre hozzáférő coroutine-ok számát egy erőforráshoz vagy egy adott kódrészlethez.
    • Engedélyek poolját kezeli.
    • acquire() és release() funkciókkal, valamint a withPermit-tel rendelkezik.
    import kotlinx.coroutines.sync.Semaphore
    import kotlinx.coroutines.sync.withPermit
    import kotlinx.coroutines.*
    
    val semaphore = Semaphore(2) // Egyszerre 2 coroutine dolgozhat
    
    suspend fun doLimitedWork(id: Int) {
        semaphore.withPermit {
            println("Coroutine $id szerzett engedélyt. Dolgozik...")
            delay(100) // Munka imitálása
            println("Coroutine $id elengedte az engedélyt.")
        }
    }
    
    suspend fun main() = coroutineScope {
        repeat(5) { i ->
            launch {
                doLimitedWork(i)
            }
        }
    }
    
  3. Atomi műveletek (kotlinx.coroutines.atomic-ból)

    • Szálbiztos műveleteket biztosít primitív típusokon és hivatkozásokon.
    • Alacsony szintű CPU utasításokat (CAS - Compare-and-Swap) használ.
    • Egyszerű műveletekhez, explicit zárak nélkül.
    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) // Várható eredmény 10000
    }
    
  4. Megosztott adatszerkezetek egypéldányos diszpécseren

    • A legkönnyebb megközelítés. A coroutine-ok futtatása egy szálon belüli kontextusban (Dispatchers.Default.limitedParallelism(1) vagy newSingleThreadContext) garantálja a kód szekvenciális végrehajtását, elkerülve az adatversenyeket. Nem nyilvánvaló primitív szinkronizáció, de a hozzáférés szekvencializálásával biztosítja a szinkronizációt.
    import kotlinx.coroutines.*
    
    val mySingleThreadContext = newSingleThreadContext("SingleThread")
    var sharedData = mutableListOf<Int>()
    
    suspend fun addToSharedData(value: Int) {
        sharedData.add(value) // Biztonságos, mivel ugyanazon a szálon fut
    }
    
    suspend fun main() = withContext(mySingleThreadContext) {
        repeat(10_000) {
            launch {
                 addToSharedData(it)
            }
        }
        println(sharedData.size) // Várható eredmény 10000
    }
    
  5. Csatornák

    • Nem primitív szinkronizációs eszközök, de biztonságos adatátvitelt tesznek lehetővé coroutine-ok között, közvetve megoldva az adatokhoz való hozzáférés szinkronizálását.
    • Lehetővé teszik adatfolyamok átadását egyik coroutine-ból a másikba.

A megközelítés kiválasztása a felhasználási szcenáriótól függ. A Mutex és Semaphore klasszikus zárolási mechanizmusokat kínál, az atomikus műveletek hatékonyak egyszerű atomikus frissítésekhez, míg az egypéldányos diszpécser akkor hasznos, ha az adatokhoz való hozzáférésnek szigorúan szekvenciálisnak kell lennie. A csatornák kommunikációra és koordinációra szolgálnak, nem közvetlenül a közös erőforrások védelmére.