CodeGym /Kursy /C# SELF /Wprowadzenie do wielowątkowości

Wprowadzenie do wielowątkowości

C# SELF
Poziom 55 , Lekcja 0
Dostępny

1. Wprowadzenie

Wielowątkowość — to jak równoległa praca kilku pracowników w biurze: jeden drukuje dokumenty, drugi dzwoni do klienta, trzeci robi kawę (i, oczywiście, wszyscy oni — programiści). Gdybyśmy mieli tylko jednego pracownika, robiłby wszystko po kolei, a biuro dusiłoby się od nudy i kolejki po kawę. W programowaniu sytuacja analogiczna: program jednoprocesowy może wykonywać tylko jedno zadanie naraz.

Wyobraźmy sobie, że nasza aplikacja wykonuje długą operację, na przykład pobiera plik z internetu albo liczy ogromną tabelę. Wszystko inne w tym czasie "zamraża się" — przyciski nie działają, animacja stoi, słowa "nie odpowiada" pojawiają się w okienku.

Wielowątkowość pozwala aplikacji robić kilka rzeczy jednocześnie: interfejs pozostaje responsywny, operacje wykonywane są równolegle i nie dostajemy monologu w stylu "Komputer, znowu się zawiesiłeś?!".

Główne pojęcia i terminologia

Zanim wskoczymy głębiej, rozbijmy, czym jest wątek (thread) i czym różni się od procesu.

  • Proces (Process): Samodzielny program z własną przestrzenią adresową, zmiennymi, zasobami. Na przykład każda uruchomiona aplikacja w Windows — to osobny proces.
  • Wątek (Thread): Jednostka wykonania wewnątrz procesu. Proces może zawierać jeden lub więcej wątków, które pracują na tych samych zasobach (pamięć, zmienne).

Dlaczego wielowątkowość budzi tyle pytań?

Bo wątki to imprezowe typy: mogą zacząć wykonywać się w dowolnym momencie, mieszać dane, przerywać sobie nawzajem i generalnie zrobić bałagan w pamięci, jeśli nie pilnujesz porządku. Jeśli wydaje się, że to wygląda jak przedszkole bez wychowawcy — to dokładnie tak! Dyscyplina i dbałość o wątki to podstawa pisania niezawodnych programów wielowątkowych.

2. Historia i rola wielowątkowości w C# i .NET

Dawno temu C# był jednowątkowy, a programy — proste. Wraz ze wzrostem wymagań wydajnościowych, pojawieniem się wielordzeniowych procesorów i potrzebą tworzenia responsywnych aplikacji bez zawieszania UI, w .NET pojawiły się narzędzia do wielowątkowości. Najpierw był klasyczny System.Threading.Thread, później pojawiły się tasks (Task), metody asynchroniczne (async/await), przetwarzanie równoległe danych (PLINQ) i wysokopoziomowe prymitywy synchronizacji.

C# urosło do potężnej platformy, gdzie wielowątkowość to nie egzotyka, a codzienność.

Wizualnie: proces i wątki

Prosty schemat:


+--------------------------------------------------+
|                 Proces (twoj program)            |
|      +-------------+   +-------------+           |
|      |   Wątek 1   |   |   Wątek 2   |           |
|      +-------------+   +-------------+           |
|                ...                                 |
|      +-------------+                              |
|      |   Wątek N   |                              |
|      +-------------+                              |
+--------------------------------------------------+

Wszystkie wątki wewnątrz procesu widzą wspólne zmienne i zasoby.

3. Jak stworzyć wątek w C#?

Zaczniemy od najbardziej podstawowego: klasy Thread z przestrzeni nazw System.Threading.

Przykład: Uruchamiamy drugi wątek

Niech mamy jakąś długotrwałą pracę — na przykład obliczanie sumy liczb od 1 do 10_000_000. Gdy trwa liczenie, główny wątek niech wypisze powitanie użytkownikowi.


using System;
using System.Threading;

class Program
{
    // Metoda dla drugiego zadania
    static void CalculateSum()
    {
        long sum = 0;
        for (int i = 1; i <= 10_000_000; i++)
            sum += i;
        Console.WriteLine($"[Wątek 2] Suma: {sum}");
    }

    static void Main()
    {
        // Tworzymy wątek, podajemy delegat na metodę
        Thread thread = new Thread(CalculateSum);
        
        thread.Start(); // Uruchamiamy drugi wątek

        // Główny wątek kontynuuje pracę
        Console.WriteLine("[Wątek 1] Cześć! Pracujemy równolegle...");

        // Czekamy na zakończenie drugiego wątku przed wyjściem
        thread.Join();

        Console.WriteLine("[Wątek 1] Wszystko zakończone!");
    }
}

Co się stanie?
Na ekranie pojawi się linia "[Wątek 1] Cześć! Pracujemy równolegle...", a potem, gdy wątek liczący skończy pracę, wypisze sumę.

Typowy problem: Kto pierwszy, ten wypisał

Uruchom ten kod kilka razy — kolejność wypisywania może być różna! Czasem suma pojawi się pierwsza, czasem powitanie. To jest prawdziwa wielowątkowość — programy stają się mniej przewidywalne, jak humor kota w poniedziałek.

4. Obszar pamięci: co widzą wątki?

Wszystkie wątki w procesie mają dostęp do tych samych zmiennych (o ile nie są lokalne dla metody). Jeśli zmienisz zmienną w jednym wątku — zobaczą ją pozostałe!

Przykład: Wspólna zmienna


using System;
using System.Threading;

class Program
{
    static int counter = 0;

    static void Increment()
    {
        for (int i = 0; i < 1000; i++)
            counter++;
    }

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"counter = {counter}");
    }
}

Ile spodziewamy się zobaczyć w counter? Logika podpowiada 2000, bo każdy wątek zwiększa po 1000.

A jednak nie! Uruchom kilka razy — zobaczysz różne wartości: 1782, 1935, 1999…
Dlaczego? To klasyczny problem Race Condition — wątki "przechwytują" sobie sterowanie między odczytem -> zwiększeniem -> zapisem, i część inkrementów się gubi.

5. Przydatne niuanse

Jak wątki współdziałają z UI?

W nowoczesnych aplikacjach desktopowych (WinForms/WPF/MAUI) główny wątek obsługuje GUI. Wszystkie akcje użytkownika (kliknięcia, wprowadzanie) — na tym wątku. Tła zadania powinny wykonywać się w innych wątkach, ale zgodnie z zasadami nie można bezpośrednio "dotykać" UI z innych wątków. To zapobiega chaosowi.

W konsoli takiego ograniczenia nie ma, można pisać do Console.WriteLine z dowolnego wątku. Jednak w rzeczywistych aplikacjach bez poprawnej synchronizacji UI może "odjechać".

Sekwencja i równoległość

Tabela — żeby utrwalić różnice.

Kod jednowątkowy Kod wielowątkowy
Wykonuje zadania po kolei Zadania mogą być wykonywane jednocześnie
UI "zawiesza się" przy długich zadaniach UI pozostaje responsywne
Prosto czytać i zapisywać zmienne Wymaga kontroli dostępu do danych
Łatwo debugować Może być trudno debugować

Ważne punkty przy pracy z wątkami

  • Wspólne zmienne — wspólne ryzyko. Jak pokazano wyżej, jeśli kilka wątków używa wspólnej zmiennej — bez synchronizacji możliwe są błędy! (Szczegóły w kolejnych wykładach.)
  • Na wątek można "czekać" przez Join(). Metoda Join() pozwala "zawiesić" główny wątek do zakończenia wątku tła. Użyj jej, jeśli trzeba poczekać na wynik.
  • Wątku nie da się uruchomić dwa razy. Po wykonaniu wątku nie można go ponownie Startować — trzeba utworzyć nowy obiekt Thread.
  • Zakończenie wątku. Wątek kończy się, gdy kończy się wykonanie jego metody. Przymusowe "zabijanie" wątków — zły pomysł (metoda Abort() od dawna uważana jest za szkodliwą i przestarzałą).

Do czego przydaje się wielowątkowość w praktyce?

  • Aplikacje UI: nie blokować interfejsu, gdy trwa tło ładowania lub obliczenia.
  • Serwery i usługi: obsługiwać jednocześnie żądania wielu klientów.
  • Wydajne obliczenia: dzielić duże zadanie (np. przetwarzanie milionów rekordów) na części i wykonywać je równolegle.
  • Gry, symulacje, przetwarzanie danych: modelować złożone systemy bez utraty wydajności.

Problemy wielowątkowości

Wielowątkowość daje moc, ale dodaje komplikacje:

  • Race Condition (stan wyścigu): gdy kilka wątków jednocześnie zmienia te same dane, wynik zależy od kolejności instrukcji, co jest nieprzewidywalne.
  • Deadlock (wzajemne blokowanie): wątki czekają na siebie i nikt nie może kontynuować pracy.
  • Starvation (głodzenie): jeden z wątków jest stale "pozbawiany" dostępu do zasobu.

W tym wykładzie tylko wspomnieliśmy główne problemy wielowątkowości, w następnych nauczymy się je rozpoznawać i unikać. Na razie — zapamiętaj, że jeśli coś "zawiesiło się", winne mogą być nie tylko bugi, ale też wątki, które "zorganizowały imprezę".

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION