Sobes.tech
Middle+

Jak implementovat synchronizaci v Kotlinu pomocí coroutin?

sobes.tech AI

Odpověď od AI

Synchronizace v Kotlin korutinách je dosažena pomocí speciálních primitiv a datových struktur z knihovny kotlinx.coroutines.

Hlavní přístupy:

  1. Mutex

    • Poskytuje vzájemné vyloučení pro ochranu kritických sekcí kódu.
    • Korutina, která se pokusí získat Mutex, je zablokována, dokud není uvolněn.
    • Má funkce lock() a unlock(), stejně jako 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čekáváme 10000
    }
    
  2. Semaphore

    • Omezí počet korutin, které mohou současně získat přístup k zdroji nebo vykonat určitý blok kódu.
    • Řídí skupinu povolení.
    • Má funkce acquire() a release(), stejně jako withPermit.
    import kotlinx.coroutines.sync.Semaphore
    import kotlinx.coroutines.sync.withPermit
    import kotlinx.coroutines.*
    
    val semaphore = Semaphore(2) // Současně může pracovat 2 korutiny
    
    suspend fun doLimitedWork(id: Int) {
        semaphore.withPermit {
            println("Korutina $id získala povolení. Pracuji...")
            delay(100) // Simulace práce
            println("Korutina $id uvolnila povolení.")
        }
    }
    
    suspend fun main() = coroutineScope {
        repeat(5) { i ->
            launch {
                doLimitedWork(i)
            }
        }
    }
    
  3. Atomické operace (z kotlinx.coroutines.atomic)

    • Poskytují bezpečné operace nad primitivními typy a odkazy.
    • Používají nízkoúrovňové CPU instrukce (CAS - Compare-and-Swap).
    • Vhodné pro jednoduché operace bez explicitního zámku.
    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čekáváme 10000
    }
    
  4. Sdílené datové struktury na dispatcheru s jedním vláknem

    • Nejjednodušší přístup. Spuštění korutin na jednovláknovém kontextu (Dispatchers.Default.limitedParallelism(1) nebo newSingleThreadContext) zaručuje sekvenční provádění kódu, čímž se vyloučí závody dat. Není to explicitní primitiv synchronizace, ale zajišťuje synchronizaci serializací přístupu.
    import kotlinx.coroutines.*
    
    val mySingleThreadContext = newSingleThreadContext("SingleThread")
    var sharedData = mutableListOf<Int>()
    
    suspend fun addToSharedData(value: Int) {
        sharedData.add(value) // Bezpečné, protože běží na jednom vlákně
    }
    
    suspend fun main() = withContext(mySingleThreadContext) {
        repeat(10_000) {
            launch {
                 addToSharedData(it)
            }
        }
        println(sharedData.size) // Očekáváme 10000
    }
    
  5. Kanály

    • Nejsou primitivy synchronizace v čisté podobě, ale mohou být použity pro bezpečný přenos dat mezi korutinami, čímž nepřímo řeší problémy synchronizace přístupu k předávaným datům.
    • Poskytují způsob přenosu toku dat z jedné korutiny do druhé.

Volba přístupu závisí na scénáři použití. Mutex a Semaphore poskytují klasické mechanismy blokování, atomické operace jsou efektivní pro jednoduché atomické aktualizace, a dispatcher s jedním vláknem je vhodný, když má být přístup k sdíleným datům striktně sekvenční. Kanály se používají pro komunikaci a koordinaci, nikoli přímo pro ochranu sdílených zdrojů.