0Pricing
Kotlin Academy · Lekcja

Mutex i Semaphore dla współdzielonego stanu

Chroń współdzielony mutowalny stan za pomocą Mutex i ograniczaj współbieżność przy użyciu Semaphore.

Mutex i Semaphore dla współdzielonego stanu to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.

Problem współdzielonego stanu

Korutyny działające na wielu wątkach mogą uszkodzić współdzielony, mutowalny stan. Bez synchronizacji współbieżne zwiększanie wartości prowadzi do nieprawidłowych wyników.

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
}

Podstawy Mutex

Mutex to blokada wzajemnego wykluczania dla korutyn. Zawiesza wykonanie wywołującego (zamiast je blokować), dopóki blokada nie będzie dostępna.

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
}

Rozszerzenie withLock

mutex.withLock { ... } przejmuje blokadę, wykonuje blok i zwalnia blokadę — także w przypadku wyjątku lub anulowania.

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())
}

Ręczne lock / unlock

Można bezpośrednio wywoływać lock() i unlock(), aby uzyskać większą kontrolę, ale należy preferować withLock, aby uniknąć pominięcia odblokowania.

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 nie jest reentrantny

W Kotlinie Mutex NIE jest reentrantny. Ponowne wywołanie lock() z tej samej korutyny spowoduje zakleszczenie. Należy użyć licznika albo zmienić strukturę kodu, aby uniknąć zagnieżdżonego blokowania.

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")
    }
}

Podstawy Semaphore

Semaphore(n) pozwala maksymalnie n korutom działać współbieżnie. Przypomina system biletowy z n miejscami.

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() }
}

Rozszerzenie withPermit

semaphore.withPermit { ... } rezerwuje pozwolenie, wykonuje blok i zwalnia pozwolenie — jest bezpiecznym dla korutyn odpowiednikiem próby przejęcia pozwolenia semafora.

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)
}

Ograniczanie częstotliwości za pomocą Semaphore

Należy użyć semafora do ograniczenia liczby współbieżnych wywołań API — na przykład do N żądań jednocześnie.

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 a Semaphore

Mutex: semafor binarny (1 pozwolenie), zapewniający wyłączny dostęp. Semaphore(n): n pozwoleń, ograniczających współbieżność. Należy używać Mutex do ochrony zasobu, a Semaphore do ograniczania równoległości.

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") }

Atomowy licznik z Mutex

Należy zaimplementować bezpieczną wątkowo klasę licznika, używając Mutex do ochrony operacji zwiększania wartości i odczytu.

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()}")
}

Alternatywa: dyspozytor jednowątkowy

Zamiast mutexu można ograniczyć mutowalny stan do dyspozytora jednowątkowego. Na tym dyspozytorze w danym momencie działa tylko jedna korutyna, dzięki czemu dostęp jest z natury bezpieczny.

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()
}

Szybkie sprawdzenie

Jaka jest główna różnica między Mutex a Semaphore?

Podsumowanie

Mutex zapewnia wyłączny dostęp do współdzielonego stanu — należy preferować withLock. Semaphore(n) ogranicza współbieżność do n korutyn. Oba mechanizmy zawieszają oczekujące korutyny, zamiast je blokować. W prostych przypadkach dyspozytor jednowątkowy często jest czytelniejszym rozwiązaniem.

Często zadawane pytania

Czy lekcja „Mutex i Semaphore dla współdzielonego stanu” jest bezpłatna?

Tak — pełny tekst „Mutex i Semaphore dla współdzielonego stanu” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Mutex i Semaphore dla współdzielonego stanu”?

Chroń współdzielony mutowalny stan za pomocą Mutex i ograniczaj współbieżność przy użyciu Semaphore. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?

Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Mutex i Semaphore dla współdzielonego stanu”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?

Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Podstawy Channel: send, receive i close
  2. Typy Channel: Rendezvous, Buffered, Conflated, Unlimited
  3. Mutex i Semaphore dla współdzielonego stanu
  4. Aktory i strukturalne zarządzanie stanem
← Powrót do Kotlin Academy