Wielowątkowość: podstawowe pojęcia – deadlock, livelock, starvation.

O wielowątkowości pisałem już niejednokrotnie. Niestety w żadnym z moich postów, nie wyjaśniłem podstawowych pojęć związanych z współbieżnością. Oczywiście jeśli wykorzystujemy wątki do prostych zadań typu asynchroniczne połączenie z usługą, poważniejszych problemów nie doświadczymy. W przypadku jednak nieco bardziej zaawansowanych algorytmów, musimy zawsze badać nasz kod pod kątem:

1. Zakleszczenie (deadlock) – występuję gdy wątek A czeka aż wątek B skończy swoją operację a wątek B czeka aż wątek A zakończy akcję. W takiej sytuacji oczywiście algorytm nigdy nie skończy operacji, ponieważ wątki czekają na siebie nawzajem. Przykład w c#:

    private readonly object objectLockA = new object();
    private readonly object objectLockB = new object();
    
    public void MethodA()
    {
        lock(objectLockA)
        {
            lock(objectLockB)
            {        
            }
        }
    }
    
    public void MethodB()
    {
        lock(objectLockB)
        {
            lock(objectLockA)
            {
            }
        }
    }
    

Załóżmy, że MethodA oraz MethodB uruchamiane są w dwóch różnych wątkach.Ponadto przypuśćmy, że aktualnie obydwu metodom udało się wykonać pierwszy lock  – lock(objectLockA) dla metody A oraz lock(objectLockB) dla metody B. I co dalej? Oczywiście algorytm się zakleszczy ponieważ w przypadku metody A nie można będzie uzyskać blokady do objectLockB (została ona już przyznana metodzie B) a w przypadku metody B sytuacja wygląda analogicznie.

2. Zagłodzenie (starvation). Mamy kilka wątków i jeśli algorytm lub architektura pozwala na to, że jeden proces nigdy nie będzie miał szansy wykonania się, wtedy mówimy o zagłodzeniu procesu. W przypadku architektury systemu, powinna ona dopuścić do wykonania również wątki o najniższym priorytecie. Jeśli algorytm szeregowania procesów jest zły, wtedy istnieje szansa, że wątek zostanie zagłodzony, ponieważ zawsze będą wykonywane inne procesy np. o wyższym priorytecie. Problem również można rozwiązać na poziomie algorytmu. Można przecież tak zaprojektować algorytm,  że będzie on dopuszczał inne wątki do wykonania (np. poprzez wykonanie funkcji Sleep w określonych momentach). Sleep jednak jest najbardziej prymitywną metodą unikania zagłodzenia i powinno się problem rozwiązać poprzez poprawny przepływ między wątkami .

3. Livelock – stanowi specjalny przypadek zagłodzenia. Występuje gdy obydwa procesy aby uniknąć deadlock’a zatrzymują wykonywanie kodu aby dać szansę innym wątkom na wykonanie się. Aby ułatwić zrozumienie tego, wyobraźmy sobie sytuację gdy dwie osoby na korytarzu aby minąć siebie wybierają ciągle tą samą trasę, kończąc na ciągłej wzajemnej blokadzie. Livelock może wydawać się podobny do deadlock (rezultat jest taki sam). W przypadku Livelock stan procesu się jednak zmienia. Z kolei w deadlock, pozostaje ciągle taki sam. W przykładzie dwóch osób na korytarzu można zauważyć, że ciągle zmieniają swoją pozycje (lewo, prawo). Analizując deadlock i powyższy fragment kodu c#  można zauważyć, że stan pozostaje taki sam -  nic się nie dzieje, po prostu czekamy na akcję, nie zmieniając swojego stanu. Livelock jest częstym problemem w niepoprawnych algorytmach wykrywania i usuwania deadlock.

Leave a Reply

Your email address will not be published.