Middle+
Как може да се реализира синхронизация в Kotlin с използване на корутини?
sobes.tech AI
Отговор от AI
Синхронизацията в Kotlin корутините се постига чрез специални примитиви и структури от данни от библиотеката kotlinx.coroutines.
Основни подходи:
-
Mutex
- Осигурява взаимно изключване за защита на критичните секции на кода.
- Корутината, която се опитва да получи Mutex, се блокира, докато не бъде освободен.
- Има функции
lock()иunlock(), както и по-идиоматичния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) // Очакваме 10000 } -
Semaphore
- Ограничават броя на корутините, които могат едновременно да получат достъп до ресурс или да изпълнят определен блок от код.
- Управлява пул от разрешения.
- Има функции
acquire()иrelease(), както иwithPermit.
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.* val semaphore = Semaphore(2) // Могат да работят едновременно 2 корутини suspend fun doLimitedWork(id: Int) { semaphore.withPermit { println("Корутината $id получи разрешение. Работи...") delay(100) // Имитиране на работа println("Корутината $id освободи разрешението.") } } suspend fun main() = coroutineScope { repeat(5) { i -> launch { doLimitedWork(i) } } } -
Атомарни операции (от
kotlinx.coroutines.atomic)- Осигуряват потокобезопасни операции върху примитивни типове и препратки.
- Използват нискоуровневите CPU инструкции (CAS - Compare-and-Swap).
- Подходящи за прости операции без явни блокировки.
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) // Очакваме 10000 } -
Общи структури от данни на dispatcher с един поток
- Най-простият подход. Изпълнението на корутините на еднопоточен контекст (
Dispatchers.Default.limitedParallelism(1)илиnewSingleThreadContext) гарантира последователното изпълнение на кода, като елиминира състезанията за данни. Не е явен примитив за синхронизация, но осигурява синхронизация чрез сериализация на достъпа.
import kotlinx.coroutines.* val mySingleThreadContext = newSingleThreadContext("SingleThread") var sharedData = mutableListOf<Int>() suspend fun addToSharedData(value: Int) { sharedData.add(value) // Безопасно, тъй като се изпълнява на един поток } suspend fun main() = withContext(mySingleThreadContext) { repeat(10_000) { launch { addToSharedData(it) } } println(sharedData.size) // Очакваме 10000 } - Най-простият подход. Изпълнението на корутините на еднопоточен контекст (
-
Канали
- Не са примитиви за синхронизация в чистата им форма, но могат да се използват за безопасен пренос на данни между корутините, което косвено решава проблемите със синхронизация на достъпа до предаваните данни.
- Предоставят начин за пренос на поток от данни от една корутина към друга.
Изборът на подход зависи от сценария на използване. Mutex и Semaphore предоставят класически механизми за блокиране, атомарните операции са ефективни за прости атомарни актуализации, а dispatcher с един поток е удобен, когато достъпът до споделените данни трябва да бъде строго последователен. Каналите се използват за комуникация и координация, а не за директна защита на споделените ресурси.