Koncepcje programowania/Wielowątkowość

Z Wikibooks, biblioteki wolnych podręczników.
Przejdź do nawigacji Przejdź do wyszukiwania

W programie, możemy mieć albo jeden wątek, albo wiele. W programie jednowątkowym, wszystko jest wykonywane w sposób liniowy, każda instrukcja wykonywana jest po kolei. Czyli możesz sobie to wyobrazić w ten sposób, że mamy jeden wątek, on ma jakiś rozbudowany zestaw instrukcji, które wykonujemy. Może ich wykonać wiele ale problemem może być jedna, bardzo długa instrukcja. Nie możemy wykonać kolejnej, następnej instrukcji, zanim ta bardzo długa i rozbudowana, się nie zakończy. Na przykład przetwarza bardzo dużą ilość danych, zajmie mu to kilka minut i przez te kilka minut musimy czekać aż program zakończy działanie, w tym czasie nie będziemy mogli niczego wykonać, kolejne instrukcje zostaną wykonane tylko i wyłącznie wtedy, gdy poprzednie zostaną zakończone. I tu pojawia się problem, że takie dłuższe instrukcje mogą powodować "zator", zablokować nasz program, nie mogąc robić kolejnych, dalszych części instrukcji programu. Na przykład wchodzimy w interakcję z programem, klikając na jakiś przycisk w programie graficznym i dopóki program nie przetworzy żądania, to musimy czekać aż go nie przetworzy. No na pewno może to być problematyczne i konfundujące użytkownika, sprawiając wrażenie że program się zawiesił. Potrzebny jest więc pewien mechanizm, który pozwoli nam wykonać wiele wątków jednocześnie, wiele akcji jednocześnie.

W aplikacji wielowątkowej mamy wciąż jeden wątek główny, ale gdy będzie potrzeba wykonania jakiejś bardzo długiej instrukcji, która zablokowałaby nasz główny wątek na dłuższy czas. W takiej sytuacji, taką bardziej rozbudowaną instrukcję moglibyśmy wstawić do drugiego, dodatkowego wątku która będzie się wykonywać w tle a w tym samym czasie pozostałe instrukcje w wątku głównym wciąż będą mogły być wykonywane - w tym samym czasie.

Zaletą takiego rozwiązania jest to, że może poprawić wydajność i responsywność aplikacji, oczywiście pod warunkiem że odpowiednio ją zaprojektujemy i że nasz język programowania to wspiera, ponieważ nie zawsze jest wbudowany w język programowania i zostaje osiągany różnymi obejściami tego problemu.

Wadą, oczywiście oprócz bardziej skomplikowanego kodu, to fakt że wątki mogą czasami konkurować o zasoby. Na przykład, masz 2 różne wątki, główny i dodatkowy i obie jednocześnie próbują zapisać dane do tego samego pliku - w rezultacie może pojawić się konflikt i żaden z nich nie uzyska dostępu do zasobu.

Przykład wielowątkowości w języku Java:

package demotest;
public class thread_example1 implements Runnable {
    @Override
    public void run() {
    }
    public static void main(String[] args) {
        Thread guruthread1 = new Thread();
        guruthread1.start();
        try {
            guruthread1.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        guruthread1.setPriority(1);
        int gurupriority = guruthread1.getPriority();
        System.out.println(gurupriority);
        System.out.println("Thread Running");
  }
}

Po wykonaniu powyższego kodu otrzymasz następujące dane wyjściowe:

5
Thread Running

5 to priorytet Thread, a Thread Running to tekst, który jest wynikiem naszego kodu.

Wyjaśnienie:

  • Linia kodu 2: Tworzymy klasę "thread_Example1", która implementuje interfejs Runnable (powinna być zaimplementowana przez dowolną klasę, której instancje mają być wykonywane przez wątek).
  • Linia kodu 4: Zastępuje metodę uruchamiania interfejsu uruchomieniowego, ponieważ jest to obowiązkowe, aby nadpisać tę metodę
  • Linia kodu 6: Tutaj zdefiniowaliśmy główną metodę, w której rozpoczniemy wykonywanie wątku.
  • Wiersz kodu 7: Tutaj tworzymy nową nazwę wątku jako „guruthread1” poprzez utworzenie instancji nowej klasy wątku.
  • Linia kodu 8: użyjemy metody "start" wątku przy użyciu instancji "guruthread1". Tutaj wątek rozpocznie wykonywanie.
  • Linia kodu 10: Tutaj używamy metody "sleep" wątku używającej instancji "guruthread1". W związku z tym wątek będzie spał przez 1000 milisekund.
  • Kod 9-14: Tutaj umieściliśmy metodę uśpienia w bloku try catch, ponieważ jest sprawdzany wyjątek, który występuje, np. Wyjątek przerwany.
  • Linia kodu 15: Tutaj ustawiamy priorytet wątku na 1 z dowolnego priorytetu
  • Linia kodu 16: Tutaj uzyskujemy priorytet wątku za pomocą getPriority()
  • Linia kodu 17: Tutaj wyświetlamy wartość pobraną z getPriority
  • Linia kodu 18: Tutaj piszemy tekst, który działa wątek.

Jak widać, może to być skomplikowane i nieoczywiste. Na szczęście istnieją języki, które pozwalają pisać programy wielowątkowe właściwie w pełni automatycznie i myślę doskonałym przykładem może być język programowania go:

Tak wygląda program jedno-wątkowy:

slice := []string{"a", "b", "c", "d", "e"}
sliceLength := len(slice)
fmt.Println("Running for loop…")
for i := 0; i < sliceLength; i++ {
    val := slice[i]
    fmt.Printf("i: %v, val: %v\n", i, val)
}
fmt.Println("Finished for loop")

A tak wielowątkowy:

slice := []string{"a", "b", "c", "d", "e"}
sliceLength := len(slice)
var wg sync.WaitGroup
// Tell the 'wg' WaitGroup how many threads/goroutines
//   that are about to run concurrently.
wg.Add(sliceLength)
fmt.Println("Running for loop…")
for i := 0; i < sliceLength; i++ {
    // Spawn a thread for each iteration in the loop.
    // Pass 'i' into the goroutine's function
    //   in order to make sure each goroutine
    //   uses a different value for 'i'.
    go func(i int) {
        // At the end of the goroutine, tell the WaitGroup
        //   that another thread has completed.
        defer wg.Done()
        val := slice[i]
        fmt.Printf("i: %v, val: %v\n", i, val)
    }(i)
}
// Wait for `wg.Done()` to be exectued the number of times
//   specified in the `wg.Add()` call.
// `wg.Done()` should be called the exact number of times
//   that was specified in `wg.Add()`.
// If the numbers do not match, `wg.Wait()` will either
//   hang infinitely or throw a panic error.
wg.Wait()
fmt.Println("Finished for loop")