0Pricing
Kotlin Academy · Урок

Mutex и Semaphore для общего состояния

Защищайте общее изменяемое состояние с помощью Mutex и ограничивайте параллелизм с помощью Semaphore.

«Mutex и Semaphore для общего состояния» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Проблема общего состояния

Сопрограммы в нескольких потоках могут повредить общее изменяемое состояние. Без синхронизации одновременное увеличение значения приводит к неверным результатам.

import kotlinx.coroutines.*
fun main() = runBlocking {
    var counter = 0
    val jobs = List(1000) {
        launch(Dispatchers.Default) { counter++ } // race condition!
    }
    jobs.forEach { it.join() }
    println("Expected 1000, got: $counter") // likely wrong
}

Основы взаимной блокировки

Mutex — это блокировка взаимного исключения для сопрограмм. Она приостанавливает, а не блокирует вызывающий код, пока блокировка не станет доступной.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    var counter = 0
    val jobs = List(1000) {
        launch(Dispatchers.Default) {
            mutex.withLock { counter++ }  // safe
        }
    }
    jobs.forEach { it.join() }
    println("Counter: $counter") // 1000
}

Расширение withLock

mutex.withLock { ... } получает блокировку, выполняет блок и освобождает её, даже если произошло исключение или отмена.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    var list = mutableListOf<Int>()
    List(10) {
        launch {
            mutex.withLock {
                list.add(it)
            }
        }
    }.forEach { it.join() }
    println(list.sorted())
}

lock / unlock вручную

Для более точного управления можно напрямую вызывать lock() и unlock(), но предпочитайте withLock, чтобы не забыть освободить блокировку.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    mutex.lock()
    try {
        println("Critical section")
    } finally {
        mutex.unlock()  // must always release
    }
}

Взаимная блокировка не является реентерабельной

Mutex в Kotlin не является реентерабельной. Повторный вызов lock() из той же сопрограммы приведёт к взаимной блокировке. Используйте счётчик или измените структуру кода, чтобы избежать вложенных блокировок.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val mutex = Mutex()
    // This deadlocks:
    // mutex.withLock { mutex.withLock { println("never") } }
    mutex.withLock {
        println("Acquired once — safe")
    }
}

Основы семафоров

Semaphore(n) позволяет не более чем n сопрограммам одновременно продолжить выполнение. Это похоже на систему билетов с n доступными местами.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val semaphore = Semaphore(3) // max 3 concurrent
    List(10) { i ->
        launch {
            semaphore.withPermit {
                println("Concurrent task $i"); delay(100)
            }
        }
    }.forEach { it.join() }
}

Расширение withPermit

semaphore.withPermit { ... } получает разрешение, выполняет блок и освобождает разрешение. Это безопасный для сопрограмм эквивалент попытки получить разрешение семафора.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
fun main() = runBlocking {
    val sem = Semaphore(2)
    val results = mutableListOf<String>()
    List(5) { i ->
        launch {
            sem.withPermit {
                delay(50)
                synchronized(results) { results.add("task-$i") }
            }
        }
    }.forEach { it.join() }
    println(results)
}

Ограничение частоты с помощью семафора

Используйте семафор, чтобы ограничивать количество одновременных вызовов API: разрешайте не более N запросов за раз.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
val apiSemaphore = Semaphore(5) // max 5 concurrent requests
suspend fun callApi(id: Int): String {
    return apiSemaphore.withPermit {
        delay(100) // simulate network
        "Response-$id"
    }
}
fun main() = runBlocking {
    val results = (1..20).map { async { callApi(it) } }.awaitAll()
    println("Got ${results.size} responses")
}

Взаимная блокировка и семафор

Mutex: двоичный семафор с одним разрешением для исключительного доступа. Semaphore(n): семафор с n разрешениями для ограниченной параллельности. Используйте взаимную блокировку для защиты ресурса, а семафор — для ограничения параллельности.

import kotlinx.coroutines.sync.*
// Mutex = Semaphore(1) for exclusive access
// Semaphore(n) = n-way concurrency limit
val exclusive = Mutex()          // one at a time
val limited   = Semaphore(3)     // three at a time
fun main() { println("Mutex for exclusion; Semaphore for rate limiting") }

Атомарный счётчик с взаимной блокировкой

Реализуйте потокобезопасный класс счётчика, используя взаимную блокировку для защиты операций увеличения и чтения.

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.*
class AtomicCounter {
    private val mutex = Mutex()
    private var count = 0
    suspend fun increment() = mutex.withLock { count++ }
    suspend fun get() = mutex.withLock { count }
}
fun main() = runBlocking {
    val counter = AtomicCounter()
    List(100) { launch { counter.increment() } }.forEach { it.join() }
    println("Count: ${counter.get()}")
}

Альтернатива: однопоточный диспетчер

Вместо взаимной блокировки изолируйте изменяемое состояние в однопоточном диспетчере. На этом диспетчере одновременно выполняется только одна сопрограмма, поэтому доступ к состоянию по своей природе безопасен.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val singleThread = newSingleThreadContext("CounterThread")
    var counter = 0
    List(1000) {
        launch(singleThread) { counter++ }
    }.forEach { it.join() }
    println("Counter: $counter") // 1000, no mutex needed
    singleThread.close()
}

Быстрая проверка

В чём состоит основное различие между Mutex и Semaphore?

Итоги

Взаимная блокировка обеспечивает исключительный доступ к общему состоянию. Предпочитайте withLock. Семафор с числом разрешений n ограничивает параллельность значением n. Оба механизма приостанавливают, а не блокируют ожидающие сопрограммы. В простых случаях однопоточный диспетчер часто оказывается более понятным решением.

Часто задаваемые вопросы

Урок «Mutex и Semaphore для общего состояния» бесплатный?

Да — полный текст урока «Mutex и Semaphore для общего состояния» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «Mutex и Semaphore для общего состояния»?

Защищайте общее изменяемое состояние с помощью Mutex и ограничивайте параллелизм с помощью Semaphore. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Mutex и Semaphore для общего состояния»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы каналов: send, receive и close
  2. Типы каналов: Rendezvous, Buffered, Conflated, Unlimited
  3. Mutex и Semaphore для общего состояния
  4. Акторы и структурированное управление состоянием
← Назад к Kotlin Academy