Procesy i wątki: praca w pojedynkę kontra technika klonów cienia

Wiedza jak minutowa autoprezentacja na rozmowę kwalifikacyjną

„Jaka jest różnica między procesem a wątkiem?”

Kiedy szykowałem się do wejścia na rynek pracy, to pytanie zawsze trafiało do „TOP 10 najczęstszych pytań na rozmowach rekrutacyjnych”. Odpowiadałem jak maszyna.

„Proces to program w trakcie wykonywania, a wątek to jednostka przepływu wykonania działająca wewnątrz procesu. Procesy nie współdzielą zasobów, natomiast wątki je współdzielą.”

Rekruter pokiwał głową, a ja wmówiłem sobie, że doskonale rozumiem tę koncepcję. Jednak dopóki w pracy nie zderzyłem się z „problemami współbieżności” (Concurrency Issue), nie miałem pojęcia, jak naprawdę przerażające jest to, co kryje się w tym zdaniu.

„Dlaczego licznik wyświetleń, który powinien rosnąć o 100, rośnie tylko o 98?” „Dlaczego strona staje, kiedy ktoś uruchomi pobieranie pliku Excel?”

Mój kod działał perfekcyjnie, gdy uruchamiałem go sam, ale kiedy weszło naraz wielu użytkowników, wszystko się posypało. Myślałem, że wystarczy dodać więcej „pracowników”, i nie wiedziałem, że wraz ze wzrostem liczby pracowników koszt zarządzania nimi (Context Switching) rośnie wykładniczo.

Kiedy pracujesz sam, panuje spokój, ale gdy pracuje wielu, zaczyna się wojna.

Fabryki i pracownicy w cyfrowym centrum logistycznym

Wróćmy do naszego uniwersum „cyfrowego centrum logistycznego”. To, że program uruchamia się w komputerze, oznacza tyle, co otwarcie w centrum logistycznym jednego „zakładu” (Factory).

1. Proces: niezależny zakład

  • Definicja: „niezależna jednostka pracy”, której system operacyjny (kierownik fabryki) przydzielił zasoby.
  • Cechy: każdy ma własny budynek. Nawet jeśli sąsiednia fabryka się zawali (błąd), moja pozostaje bezpieczna.
  • Koszt: żeby postawić fabrykę, potrzeba działki (pamięci) i trzeba zarejestrować działalność (PID). „To drogie i ciężkie”.
  • Współdzielenie: co do zasady nie można zabrać rzeczy z sąsiedniej fabryki. (Trzeba użyć skomplikowanej komunikacji zwanej IPC).

2. Wątek (Thread): pracownicy w zakładzie

  • Definicja: „pracownik”, który faktycznie wykonuje pracę wewnątrz procesu (zakładu).
  • Cechy: w jednej fabryce może pracować wielu pracowników (wielowątkowość).
  • Koszt: zatrudnienie nowego pracownika jest dużo „tańsze i lżejsze” niż budowa nowej fabryki.
  • Współdzielenie: to jest najważniejsze. Pracownicy korzystają wspólnie ze „stołówki (Heap) i pokoju socjalnego (Data)”.
Wątek ma jednocześnie „przestrzeń współdzieloną” (Heap) i „przestrzeń własną” (Stack).

[Code Verification] Tragedia współdzielenia (problem współbieżności)

„Wątki współdzielą zasoby”. Na rozmowie rekrutacyjnej brzmiało to jak zaleta, ale w prawdziwych projektach może stać się zalążkiem „katastrofy”.

Pamiętasz „stertę” (Heap), którą poznaliśmy ostatnio? Wątki współdzielą ten obszar sterty. Oznacza to, że wątek B może przyjść i nadpisać dane, na których właśnie pracuje wątek A.

Nazywa się to „sytuacją wyścigu” (Race Condition). Sprawdźmy to w kodzie.

public class RaceConditionTest {
    static int count = 0; // zmienna współdzielona przechowywana na stercie (Heap)

    public static void main(String[] args) throws InterruptedException {
        // zatrudniamy dwóch pracowników (wątki)
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) count++;
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) count++;
        });

        t1.start(); // start pracy!
        t2.start(); // start pracy!
        
        t1.join(); // czekamy, aż skończy zmianę
        t2.join(); // czekamy, aż skończy zmianę
        
        System.out.println("Wynik końcowy: " + count);
    }
}

Oczekiwany wynik: dwóch pracowników dodało po 10 000 razy, więc powinno wyjść 20,000.

Rzeczywisty wynik: 15,482, 18,931… Przy każdym uruchomieniu wartość jest inna i nie wychodzi 20 000.

Przyczyna:

  1. Wątek A odczytuje ze sterty count(100) i przenosi go na stos.
  2. W międzyczasie wątek B też odczytuje ze sterty count(100).
  3. Wątek A zmienia wartość na 101 i zapisuje ją na stercie.
  4. Wątek B (skoro odczytał 100) również zmienia wartość na 101 i zapisuje ją na stercie.
  5. Dodawanie wykonano dwa razy, a wynik wygląda tak, jakby dodano tylko raz.

Oto, co naprawdę stoi za błędami, przez które w praktyce „dane czasem gdzieś giną”. To tragedia, do której dochodzi, gdy pracownicy, nie rozmawiając ze sobą, majstrują przy tej samej księdze.

Gdyby porównać tę sytuację do bazy danych (DB), to jest dokładnie to samo, co „wiele zapytań wysyłanych jednocześnie bez transakcji (Transaction)”. Saldo konta jest modyfikowane jednocześnie bez żadnego zabezpieczenia (Lock), więc dochodzi do koszmarnego wypadku, w którym pieniądze po prostu wyparowują. Tak jak w bazie danych chronimy dane za pomocą ROLLBACK czy COMMIT, tak i na poziomie kodu niezbędny jest mechanizm blokady, taki jak Synchronized.

Trade-off w praktyce: wieloprocesowość kontra wielowątkowość

Kiedy więc w praktyce wybrać jedno, a kiedy drugie?

1. Wybór przeglądarki Chrome: wieloprocesowość

Dawny Internet Explorer zamykał się w całości, gdy zawiesiła się jedna karta (podejście wielowątkowe). Chrome natomiast uruchamia każdą kartę jako „osobny proces (fabrykę)”.

  • Zaleta: nawet jeśli jedna karta przestanie odpowiadać (fabryka wyleci w powietrze), pozostałe karty (sąsiednie fabryki) działają bez zarzutu. (Stabilność)
  • Wada: pożera ogromne ilości pamięci. Właśnie dlatego Chrome stał się „hipopotamem pożerającym RAM”. (Koszt)

2. Wybór serwerów webowych (Spring, Node.js itp.): wielowątkowość

Serwer musi obsługiwać żądania tysięcy osób. Gdyby dla każdego żądania stawiać nowy proces (fabrykę), serwer by padł. Dlatego w jednym procesie trzyma się mnóstwo wątków (pracowników), które te żądania obsługują.

  • Zaleta: zużywa mało zasobów i działa szybko. (Wydajność)
  • Wada: jeśli jeden wątek rzuci błąd i zabije proces, pada cały serwer. Do tego programista musi sam zapobiegać problemom współbieżności, które widzieliśmy przed chwilą (Synchronized).
Zbudować bezpieczną, ale drogą „twierdzę” czy wypuścić szybkie, ale ryzykowne „drony”?

Na koniec: z techniką klonów cienia wiąże się odpowiedzialność

Dziś przyjrzeliśmy się „procesom i wątkom”, czyli temu, jak pracują nasi pracownicy.

  • Proces: bezpieczny, ale drogi, niezależny zakład.
  • Wątek: lekki i szybki współpracownik, ale z ryzykiem kolizji.

Teraz już wiesz, że „problemy współbieżności” to nie tylko termin na rozmowę rekrutacyjną. Przez wspólny magazyn zwany stertą (Heap) bez przerwy przewijają się tłumy wątków-pracowników. Zaprowadzenie porządku w tym chaosie to miara umiejętności programisty backendowego.

Ale chwileczkę: gdy pracowników jest coraz więcej, jak ustalić, czyja praca ma pierwszeństwo? Jak kierownik fabryki (OS) zarządza setkami pracowników? Następnym razem opowiem o jego najbardziej kłopotliwym zadaniu: „szeregowaniu zadań i przełączaniu kontekstu” (Context Switching).

Dodaj komentarz