Automatycznie przydzielanie zadań do wątków

Automatycznie przydzielanie zadań do wątków
AD
  • Rejestracja:około 14 lat
  • Ostatnio:ponad 12 lat
  • Postów:7
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

Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:17 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
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.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
Olamagato
Program nie musi wiedzieć co się dzieje poza JVM. Grunt, żeby JVM dobrze modelował ten świat w swoim środowisku. A robi to przyzwoicie na pingwinach i oknach (tym łatwiej że obsługa wątków jest od pewnego czasu natywna). Gdyby pytanie padło rok temu, to napisałbym, że najlepszy model dawałby Executors.newFixedThreadPool() z parametrem System.getAvailableProcessors() i sensownie zmajstrowaną własną fabryką. Ale dzisiaj nawet to nie jest aktualne.
Kerai
  • Rejestracja:ponad 16 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:London
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

edytowany 1x, ostatnio: Kerai
Olamagato
  • Rejestracja:ponad 16 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Polska, Warszawa
  • Postów:1058
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.


Jeżeli ktoś komuś coś, ewentualnie nikt nikomu nic, to właściwie po co...?
edytowany 4x, ostatnio: Olamagato
Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:17 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
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.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
Olamagato
Prawda, ale ktoś kto ma projekt tej wielkości, raczej nie wypisuje prośby o tego typu pomoc na forum. A jeżeli chodzi o aplikacje nowe, to nie ma żadnych ogólnych przeciwwskazań żeby od razu przejść na siódemkę. To jest właśnie jedna z zalet przejścia.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.