niedziela, 29 czerwca 2014

Wielowątkowość - używanie ThreadPoolExecutora

Powiązanie między zadaniami a ich polityka wykonywania

 

Można powiedzieć że ExecutorService rozdziela tworzenie zadań od ich wykonywania - jednak jest to prawdą tylko wtedy gdy zadania są niezależne.
Nie wszystkie zadania można łatwo wywoływać używając ExecutorService np:
  • zadania zależne - jeśli jedno zadanie zależy od innego to w przypadku Executora z ograniczoną liczbą wątków może dojść do zakleszczenia: thread starvation deadlock, najprostszym przykładem jest wykonywanie zadania na Executorze ograniczonym do jednego wątku które wywołuje inne zadanie na tej samej "puli" i czekanie na wynik. Taki wątek będzie czekał w nieskończoność bo wątek na którego czeka nigdy się nie zacznie - należy pamiętać że najlepiej w tym wypadku stosować executor z nieograniczoną liczbą wątków
  • zadania które wykorzystują "przywiązanie do wątku" (thread confinement) - takie zadania powinny być wykonywane na executorze z jednym wątkiem - zmiana liczby wątków może spowodować duże problemy
  • zadania które wykorzystują zmienne ThreadLocal - należy pamiętać że dla każdego zadania nie jest tworzony nowy wątek - jest on wykorzystywany ponownie, dodawane są też nowe i usuwane nieużywane. To powoduje że jest sens korzystania z tych zmiennych tylko jeśli zadanie zawsze wyczyści daną zmienną przed zakończeniem działania
  • zadania które powinny się skończyć szybko - niektóre zadania powinny się skończyć szybciej od innych np zadanie związane z GUI - jeśli Executor jest zapełniony długimi zadaniami tą zadanie GUI może na tym ucierpieć
Najlepszymi zadaniami do wykonywania w jednym Executorze są zadania o podobnej wielkości i niezależne.

Zrównolegalnie algorytmów rekursywnych

 

Jeśli wykonujemy pętlę i jej iteracje nie zależą od siebie a praca w nich wykonywana jest na tyle duża że jej wykonanie równoważy pracę związaną z tworzeniem nowego zadania można ją wykonać równolegle:

public void processSequencially(List<String> elements) {
        for (String element : elements) {
            processElement(element);
        }
    }
public void processInParallel(List<String> elements) {
        for (final String element : elements) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    processElement(element);
                }
            });
           
        }
    }


Tak samo jak zrównoleglenie pętli można zrównoleglić algorytm rekurencyjny - na takich samych warunkach niezależności:

    public void recursiveSequencial(List<Node> nodes, List<String> results) {
        for (Node node : nodes) {
            processNode(node, results);
            recursiveSequencial(node.getChildren(), results);
        }
    }
   
    public void recursiveParallel(List<Node> nodes, final List<String> results) {
        for (final Node node : nodes) {
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    processNode(node, results);
                }
            });
            processNode(node, results);
           
            recursiveParallel(node.getChildren(), results);
        }
    }


Należy pamiętać że kiedy submitujemy zadania do executora należy poczekać na skończenie zadań przez CompletionService lub wykonując zamykanie normalne - sutdown i awaitTermination wtedy wszystkie zadania zostaną zakończone.

Brak komentarzy:

Prześlij komentarz