Automatycznie przydzielanie zadań do wątków

1

Witam,

Mam taki problem.

Chciałbym w aplikacji określić liczbę wątków np. 3.
Mam około powiedzmy 30 zadań, które mogą być wykonywane niezależnie od siebie, ale zanim program przejdzie dalej wszystkie muszą zostać wykonane. Liczba wątków jest dość mała, gdyż każde zadanie potrzebuje sporej ilości pamięci RAM. Gdybym uruchomił 30 na raz to program spowolniłby cały komputer, gdyż na pewno zabrakłoby pamięci...

Zrobiłem już coś takiego, że tworzę 3 wątki, dodaje 3 pierwsze zadania ale następnie czekam (z metodą .join()) aż te trzy się skończą i dopiero uruchamiam kolejne trzy.

Moje pytanie jest. Czy w Javie można zrobić coś takiego, że uruchamiam 3 zadania na 3 wątkach a gdy jedno z zadań zakończy się automatycznie przydzielam następne zadanie tak, aby zawsze pracowała określona liczba wątków? Chodzi po prostu oto, aby maksymalnie wykorzystać określoną liczbę wątków.

Drugie pytanie czy w takim wypadku nie lepiej wykorzystać programowanie równoległe i rozdzielić zadania na poszczególne procesory? Czy w takim razie w Javie można wtedy automatycznie przydzielać zadania do procesorów?

Z góry dzięki za pomoc!

Pozdrawiam,
adalgrim

1

Odpowiedź na pytanie numer dwa brzmi nie. Program w Javie nie wie tak naprawdę nic o tym jak wygląda świat poza JVM.

Odpowiedź na pytanie brzmi ExecutorService, ThreadPoolExecutor oraz CountDownLatch. To wszystko dostępne w standardowym API języka.

0
adalgrim napisał(a)

Moje pytanie jest. Czy w Javie można zrobić coś takiego, że uruchamiam 3 zadania na 3 wątkach a gdy jedno z zadań zakończy się automatycznie przydzielam następne zadanie tak, aby zawsze pracowała określona liczba wątków? Chodzi po prostu oto, aby maksymalnie wykorzystać określoną liczbę wątków.

Drugie pytanie czy w takim wypadku nie lepiej wykorzystać programowanie równoległe i rozdzielić zadania na poszczególne procesory? Czy w takim razie w Javie można wtedy automatycznie przydzielać zadania do procesorów?

http://www.ibm.com/developerworks/library/j-jtp0730/index.html

1

Jest obecnie tylko jedna naprawdę dobra odpowiedź, która wyczerpuje całkowicie Twoje pytanie i pojawiła się oficjalnie pół roku temu. Brzmi ona:
"ForkJoinPool" - z zadaniami, które dziedziczą po ForkJoinTask<V> (typowo RecursiveTask i RecursiveAction, ale nie koniecznie):
http://download.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
http://download.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.html
http://download.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinTask.html
Trochę info z czasów, kiedy ten executor nie był jeszcze częścią Javy:
http://www.javacodegeeks.com/2011/02/java-forkjoin-parallel-programming.html

Ta pula wątków domyślnie obsługuje ilość wątków równą ilości dostępnych procesorów, ale można to zmienić i wymusić od 1 do 32K wątków w puli. Jeżeli zadania równomiernie dzielą zadanie główne na części, to każdy z wątków będzie rozplanowany przez systemowego managera wątków na osobnym rdzeniu/procesorze. W efekcie dostaniesz dokładnie to czego potrzebujesz - maksymalną wydajność skalowaną ilością rdzeni. W przypadku procesora jednojajowego wszystko sprowadzi się do sekwencyjnego wywoływania kolejnych utworzonych obiektów zadań (np. przez fork(), invoke()) bo wątek roboczy w puli będzie tylko jeden. Tak więc na takim procku będzie niewielki narzut marnowanej mocy przetwarzania, ale za to na maszynach wieloprocesorowych całe zadanie wykona się tak szybko jak dobrze uda Ci się podzielić zadanie na pod-zadania. Zadania można robić zupełnie odmienne i niezależne od siebie. W takim wypadku trudniej tak wszystko dopasować, aby ich czas wykonywania był mniej więcej podobny. Jednak nawet jeżeli to się nie uda, to każde zadanie, które się szybciej zakończy zwolni wątek, który pobierze sobie z wewnętrznej kolejki następne zadanie do wykonania. Dlatego im mniej jest dostępnych wątków, tym ważniejsze jest aby zadania były podobnej długości i jednocześnie niezbyt krótkie. Bo tylko mocno obciążone wątki są przerzucane przez systemowy manager wątków pod kontrolę różnych rdzeni. Z drugiej strony jeżeli zadań jest dużo (30 spokojnie wystarczy), to ten wykonawca niemal to gwarantuje.
Zrywa on ostatecznie powiązanie zadania z wątkiem, co jest zaletą bo wreszcie nie trzeba zajmować się zarządzaniem wątkami, na rzecz zajęcia się dobrym rozplanowaniem podziału pracy.
Teraz praktycznie każdy algorytm można spróbować przerobić na wersję równoległą. Dzięki temu wykonawcy jest to dużo łatwiejsze w Javie niż kiedykolwiek wcześniej i łatwiejsze niż w jakimkolwiek języku, który podobnego mechanizmu w ogóle nie ma.

Jakie minusy?

  1. Jest utrudnione korzystanie z synchronizatorów takich jak CountDownLatch, CyclicBarrier bo w skrajnym przypadku jednego wątku (czyli jednego procesora) cały mechanizm ForkJoinTasków musi ostro kombinować, żeby się nie zablokować lub nie zakleszczyć (a to kosztuje trochę magii i może się nie udać). Rozwiązanie jest proste - nie trzeba ich wcale używać.
  2. Przy nieumiejętnym podziale zadania - szczególnie jeżeli wadliwie wykorzysta się dynamiczne zarządzaniem podziałami zadania - można zająć za dużo zasobów na raz, co zwykle kończy się wysypką StackOverflow. Na szczęście pierwsza próba złego rozdziału od razu sypie wyjątkami, więc wiadomo że się skaszaniło. Z drugiej strony stackTrace nie za wiele daje informacji o miejscu sypnięcia ponieważ nierzadko ma naturę taką jak samo zadanie - rekurencyjną.
    Ale coś za coś.

ps. Zapomniałem jeszcze napisać, że ForkJoinTask<V> posiada trzy bardzo przydatne metody "adapt", które konwertują zwykłe zadania Runnable lub Callable.

0

@Olamagato, ale to jest dostępne dopiero od wersji 7. Zatem jeżeli masz możliwość wykorzystania 7 to ok. Jednak nie znam jeszcze żadnego większego projektu, który by przeszedł w pełni na Javę 7. Ona jeszcze się nieuleżała.

1 użytkowników online, w tym zalogowanych: 0, gości: 1