Sobes.tech
Middle+

Dlaczego nie zaleca się używania bloków synchronizacji i podobnych konstrukcji podczas pracy z korutynami?

sobes.tech AI

Odpowiedź od AI

Używanie konstrukcji blokujących, takich jak synchronized lub lock z biblioteki standardowej, podczas pracy z korutynami prowadzi do nieefektywnego wykorzystania zasobów i może powodować problemy.

Główne przyczyny:

  • Blokada wątku: Bloki synchronizacyjne zatrzymują wykonanie wątku, nie tylko korutyn. Jeśli w bloku synchronizowanym wykonywana jest funkcja Suspending, wątek zostanie zablokowany do czasu zakończenia funkcji Suspending i zwolnienia blokady. Może to prowadzić do "głodzenia" innych korutyn korzystających z tego samego wątku.
  • Niekompatybilność z funkcjami Suspending: Mechanizm pracy korutyn (zmiana kontekstu, kontynuacje) nie uwzględnia blokad na poziomie wątków. Wykonanie funkcji Suspending w bloku synchronizowanym narusza nieblokującą naturę korutyn.
  • Naruszenie zasad strukturalnej współbieżności: Korutyny mają własny system oznaczeń anulowania i hierarchię. Blokady na poziomie wątków ignorują ten system, utrudniając poprawne obsługiwanie anulowań i zarządzanie cyklem życia.
  • Ryzyko deadlocków: Połączenie blokad wątków i mechanizmów współbieżności korutyn zwiększa prawdopodobieństwo wystąpienia złożonych deadlocków, które są trudne do debugowania.

Zamiast blokad na poziomie wątku do synchronizacji dostępu do zasobów współdzielonych w korutynach, należy używać specjalistycznych prymitywów współbieżności z biblioteki kotlinx.coroutines:

  • Mutex: Zapewnia wzajemne wykluczanie. Korutyna próbująca przejąć zajęty mutex zostanie zawieszona (bez blokowania wątku) do czasu zwolnienia mutex.
  • Semaphore: Ogranicza liczbę korutyn mających jednoczesny dostęp do zasobu.
  • Channel: Używany do wymiany danych między korutynami.
  • Zmienne atomowe: Do prostych operacji atomowych.

Przykład użycia Mutex:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.*

val mutex = Mutex()
var counter = 0

suspend fun incrementCounter() {
    mutex.withLock {
        // Sekcja krytyczna
        counter++
    }
}

fun main() = runBlocking {
    // Tworzymy wiele korutyn do dostępu współbieżnego
    repeat(1000) {
        launch {
            incrementCounter()
        }
    }
    delay(1000) // Dajemy czas na wykonanie korutynom
    println("Counter: $counter") // Oczekiwane 1000
}

mutex.withLock jest funkcją suspending. Jeśli mutex jest zajęty, korutyna wywołująca withLock zostanie zawieszona i zwolni wątek dla innych zadań. Gdy mutex stanie się dostępny, korutyna zostanie wznowiona. To nie blokuje wątku, w przeciwieństwie do synchronized.