Sobes.tech
Middle+

Как може да се реализира синхронизация в Kotlin с използване на корутини?

sobes.tech AI

Отговор от AI

Синхронизацията в Kotlin корутините се постига чрез специални примитиви и структури от данни от библиотеката kotlinx.coroutines.

Основни подходи:

  1. 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
    }
    
  2. 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)
            }
        }
    }
    
  3. Атомарни операции (от 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
    }
    
  4. Общи структури от данни на 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
    }
    
  5. Канали

    • Не са примитиви за синхронизация в чистата им форма, но могат да се използват за безопасен пренос на данни между корутините, което косвено решава проблемите със синхронизация на достъпа до предаваните данни.
    • Предоставят начин за пренос на поток от данни от една корутина към друга.

Изборът на подход зависи от сценария на използване. Mutex и Semaphore предоставят класически механизми за блокиране, атомарните операции са ефективни за прости атомарни актуализации, а dispatcher с един поток е удобен, когато достъпът до споделените данни трябва да бъде строго последователен. Каналите се използват за комуникация и координация, а не за директна защита на споделените ресурси.