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.