Middle+
Kako implementirati sinhronizaciju u Kotlinu koristeći korutine?
sobes.tech АИ
Одговор од АИ
Sinhronizacija u Kotlin korutinama se postiže pomoću specijalnih primitiva i struktura podataka iz biblioteke kotlinx.coroutines.
Glavni pristupi:
-
Mutex
- Obezbeđuje međusobno isključivanje za zaštitu kritičnih sekcija koda.
- Korutina koja pokuša da dobije Mutex blokira se dok se on ne oslobodi.
- Ima funkcije
lock()iunlock(), kao i idiomatskiwithLock.
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čekujemo 10000 } -
Semaphore
- Ograničava broj korutina koje mogu istovremeno da pristupe resursu ili izvrše određeni blok koda.
- Upravlja skupom dozvola.
- Ima funkcije
acquire()irelease(), kao iwithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Istovremeno mogu raditi 2 korutine suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Korutina $id je dobila dozvolu. Radi...") delay(100) // Imitacija rada println("Korutina $id je oslobodila dozvolu.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
Atomicne operacije (iz
kotlinx.coroutines.atomic)- Obezbeđuju thread-safe operacije nad primitivnim tipovima i referencama.
- Koriste niskonivojske CPU instrukcije (CAS - Compare-and-Swap).
- Pogodne za jednostavne operacije bez eksplicitnih blokada.
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čekujemo 10000 } -
Deljene strukture podataka na dispatcheru sa jednim nitom
- Najjednostavniji pristup. Pokretanje korutina na kontekstu sa jednim nitom (
Dispatchers.Default.limitedParallelism(1)ilinewSingleThreadContext) garantuje sekvencijalno izvršavanje koda, izbegavajući trke podataka. Nije to eksplicitni primitivan za sinhronizaciju, ali obezbeđuje sinhronizaciju putem serializacije pristupa.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Sigurno, jer se izvršava na jednom thread-u } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Očekujemo 10000 } - Najjednostavniji pristup. Pokretanje korutina na kontekstu sa jednim nitom (
-
Kanali
- Nisu primitives za sinhronizaciju u čistom obliku, ali se mogu koristiti za sigurnu transmisiju podataka između korutina, što indirektno rešava probleme sinhronizacije pristupa podacima.
- Omogućavaju prenos toka podataka iz jedne korutine u drugu.
Izbor pristupa zavisi od scenarija korišćenja. Mutex i Semaphore pružaju klasične mehanizme blokiranja, atomarne operacije su efikasne za jednostavne atomarne ažuriranja, a dispatcher sa jednim thread-om je zgodan kada pristup zajedničkim podacima mora biti strogo sekvencijalni. Kanali se koriste za komunikaciju i koordinaciju, a ne za direktnu zaštitu zajedničkih resursa.