2012-10-17 15 views
6

Tak więc mam program, który zrobiłem, który musi wysłać dużo (np. 10 000+) żądań GET do adresu URL i potrzebuję go, aby był tak szybki, jak to możliwe. Kiedy po raz pierwszy stworzyłem program, po prostu wstawiłem połączenia do pętli for, ale było to naprawdę powolne, ponieważ przed kontynuowaniem trzeba było poczekać na zakończenie każdego połączenia. Chciałem zrobić to szybciej, więc próbowałem używać wątków i zrobiłem to trochę szybciej, ale wciąż nie jestem usatysfakcjonowany.Zrozumienie wątków + Asynchroniczny

Zgaduję, poprawny sposób, aby przejść o tym i bardzo szybko jest za pomocą asynchronicznego połączenia i łączenia się z wszystkimi adresami URL. Czy to właściwe podejście?

Staram się również zrozumieć wątki i sposób ich działania, ale nie mogę tego uzyskać. Komputer, na którym pracuję, ma czterordzeniowy procesor Intel Core i7-3610QM. Według strony internetowej Intela dla specyfikacji tego procesora, ma 8 wątków. Czy to oznacza, że ​​mogę utworzyć 8 wątków w aplikacji Java i wszystkie będą działać jednocześnie? Czy jest więcej niż 8 i nie będzie wzrostu prędkości?

Co dokładnie oznacza numer obok "wątków" w menedżerze zadań w zakładce "Wydajność"? Obecnie mój menedżer zadań pokazuje "Wątki" jako ponad 1000. Dlaczego to jest ten numer i jak może przekroczyć 8, jeśli to wszystko obsługuje mój procesor? Zauważyłem także, że kiedy próbowałem mojego programu z 500 wątkami jako testem, liczba w menedżerze zadań wzrosła o 500, ale miała taką samą prędkość, jak gdybym ustawił go do używania 8 wątków zamiast. Więc jeśli liczba rośnie w zależności od liczby wątków, których używam w mojej aplikacji Java, dlaczego prędkość jest taka sama?

Próbowałem także zrobić mały test z wątkami w Javie, ale dane wyjściowe nie mają dla mnie sensu. Oto moja klasa Test:

import java.text.SimpleDateFormat; 
import java.util.Date; 

public class Test { 

    private static int numThreads = 3; 
    private static int numLoops = 100000; 
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("[hh:mm:ss] "); 

    public static void main(String[] args) throws Exception { 

     for (int i=1; i<=numThreads; i++) { 
      final int threadNum = i; 
      new Thread(new Runnable() { 
       public void run() { 
        System.out.println(dateFormat.format(new Date()) + "Start of thread: " + threadNum); 
        for (int i=0; i<numLoops; i++) 
         for (int j=0; j<numLoops; j++); 
        System.out.println(dateFormat.format(new Date()) + "End of thread: " + threadNum); 
      } 
      }).start(); 
      Thread.sleep(2000); 
     } 

    } 
} 

To powoduje wyjście takie jak:

[09:48:51] Start of thread: 1 
[09:48:53] Start of thread: 2 
[09:48:55] Start of thread: 3 
[09:48:55] End of thread: 3 
[09:48:56] End of thread: 1 
[09:48:58] End of thread: 2 

Dlaczego trzeci początek i koniec gwintu od razu, podczas gdy pierwszy i drugi ma 5 sekund każdego? Jeśli dodaję więcej niż 3 wątki, to samo dzieje się dla wszystkich wątków powyżej 2.

Przepraszam, jeśli to była długa lektura, miałem wiele pytań. Z góry dzięki.

Odpowiedz

9

Twój procesor ma 8 rdzeni, a nie nici. W rzeczywistości oznacza to, że tylko 8 rzeczy może działać w danym momencie. Nie oznacza to jednak, że jesteś ograniczony tylko do 8 wątków.

Gdy wątek synchronicznie otwiera połączenie z adresem URL, często będzie spał podczas oczekiwania na zdalny serwer, aby do niego wrócić. Podczas, gdy wątek śpi, inne wątki mogą wykonywać pracę. Jeśli masz 500 wątków, a wszystkie 500 śpią, to nie używasz żadnego z rdzeni twojego procesora.

Po drugiej stronie, jeśli masz 500 wątków i wszystkie 500 wątków chcą coś zrobić, to nie wszystkie mogą działać jednocześnie. Aby obsłużyć ten scenariusz istnieje specjalne narzędzie. Procesory (lub, co bardziej prawdopodobne, system operacyjny lub ich kombinacja) mają harmonogram, który określa, które wątki aktywnie działają na procesorze w danym momencie. Istnieje wiele różnych reguł, a czasem także losowa aktywność, która kontroluje działanie tych harmonogramów. Może to wyjaśniać, dlaczego w powyższym przykładzie wątek 3 zawsze wydaje się kończyć jako pierwszy. Być może program planujący preferuje wątek 3, ponieważ był to ostatni wątek zaplanowany przez wątek główny, czasami nie można przewidzieć zachowania.

Teraz, aby odpowiedzieć na twoje pytanie dotyczące wydajności.Jeśli otwarcie połączenia nigdy nie wiązałoby się ze snem, nie ma znaczenia, czy zajmowałeś się synchronicznie lub asynchronicznie, że nie uzyskasz żadnego przyrostu wydajności powyżej 8 wątków. W rzeczywistości wiele czasu związanego z otwarciem połączenia spędza się śpiąc. Różnica między synchronizacją asynchroniczną a synchroniczną polega na tym, jak radzić sobie z tym spaniem. Teoretycznie powinieneś być w stanie uzyskać niemal równą wydajność między tymi dwoma.

Model wielowątkowy tworzy po prostu więcej nitek niż rdzeni. Kiedy wątki trafiają w sen, pozostałe wątki działają. Czasami może to być łatwiejsze w obsłudze, ponieważ nie trzeba pisać żadnych harmonogramów ani interakcji między wątkami.

W modelu asynchronicznym tworzy się tylko jeden wątek na rdzeń. Jeśli ten wątek wymaga uśpienia, to nie śpi, ale musi mieć kod, który poradzi sobie z przejściem do następnego połączenia. Załóżmy na przykład, istnieją trzy etapy otwarcia połączenia (A, B, C):

while (!connectionsList.isEmpty()) { 
    for(Connection connection : connectionsList) { 

    if connection.getState() == READY_FOR_A { 
     connection.stepA(); 
     //this method should return immediately and the connection 
     //should go into the waiting state for some time before going 
     //into the READY_FOR_B state 
    } 
    if connection.getState() == READY_FOR_B { 
     connection.stepB(); 
     //same immediate return behavior as above 
    } 
    if connection.getState() == READY_FOR_C { 
     connection.stepC(); 
     //same immediate return behavior as above 
    } 
    if connection.getState() == WAITING { 
     //Do nothing, skip over 
    } 
    if connection.getState() == FINISHED { 
     connectionsList.remove(connection); 
    } 
    } 
} 

Zauważ, że w żadnym momencie nie sen wątku, więc nie ma sensu posiadania więcej wątków niż masz rdzeni. Ostatecznie, czy podejście synchroniczne, czy podejście asynchroniczne jest kwestią osobistych preferencji. Tylko w absolutnych skrajnościach wystąpią różnice w wydajności między nimi i będziesz musiał spędzić długie profilowanie, aby dojść do punktu, w którym jest to wąskie gardło w twoim wniosku.

Wygląda na to, że tworzysz wiele wątków i nie uzyskujesz żadnego przyrostu wydajności. Przyczyn tego może być wiele.

  • Możliwe, że nawiązanie połączenia w rzeczywistości nie jest w trybie uśpienia, w którym to przypadku nie spodziewam się wzrostu wydajności po 8 wątkach. Nie sądzę, żeby to było prawdopodobne.
  • Możliwe, że wszystkie wątki używają jakiegoś wspólnego zasobu udostępnionego. W tym przypadku inne wątki nie mogą działać, ponieważ wątek wątku ma zasób udostępniony. Czy istnieje jakiś obiekt, który dzielą wszystkie wątki? Czy ten obiekt ma jakieś zsynchronizowane metody?
  • Możliwe, że masz własną synchronizację. Może to spowodować problem wspomniany powyżej.
  • Jest możliwe, że każdy wątek musi wykonać jakąś pracę związaną z ustawieniami/alokacją, która pokonuje korzyści, które zyskujesz, używając wielu wątków.

Gdybym był tobą, użyłbym narzędzia takiego jak JVisualVM, aby profilować twoją aplikację z małą liczbą wątków (20). JVisualVM ma ładny kolorowy wykres nici, który będzie się wyświetlał, gdy wątki będą działać, blokować lub spać. Pomoże to zrozumieć relację wątku/rdzenia, ponieważ powinieneś zauważyć, że liczba uruchomionych wątków jest mniejsza niż liczba rdzeni, które posiadasz. Ponadto, jeśli zobaczysz wiele zablokowanych wątków, które mogą pomóc Ci w wąskim gardle (jeśli zobaczysz wiele zablokowanych wątków, użyj JVisualVM do utworzenia zrzutu wątku w tym momencie i zobacz, jakie wątki są blokowane).

+0

Dzięki za odpowiedź. Edytowanie ... – user1203585

+0

Ahh, nie mogę edytować tego komentarza w rzeczywistości ... 5-minutowy limit ... "Czy istnieje obiekt, który współdziałają wszystkie wątki? Czy ten obiekt ma jakiekolwiek zsynchronizowane metody?" Wszystkie moje wątki robią to samo: Powoduje utworzenie instancji obiektu URL i otwarcie połączenia z serwerem proxy. Ustawia limit czasu połączenia i odczytu URLConnection. Następnie używa BufferedReader i InputStreamReader do odczytu z URLConnection. Na koniec zapisuje słowo do pliku tekstowego. To, co robi każdy wątek i działa 500 z tych wątków nie wydaje się przyspieszyć:/ – user1203585

+1

Zrobiłem trochę kopania wokół. Podejrzewam, że Java ma bazową pulę połączeń o ograniczonym rozmiarze. Istnieje właściwość sieciowa o nazwie http.maxConnections [patrz tutaj] (http://docs.oracle.com/javase/1.4.2/docs/guide/net/properties.html). Wartość domyślna to 5. Oznacza to, że po otwarciu więcej niż 5 połączeń używają tych samych 5 bazowych gniazd (zasobów udostępnionych) i wszystkich połączeń, które zostaną otwarte po tym, jak zostaną zablokowane. Ponownie, możesz użyć JVisualVM, aby to potwierdzić. – Pace

1

Niektóre pojęcia:

można mieć wiele wątków w systemie, ale tylko niektóre z nich (max 8) w Twoim przypadku będzie „zaplanowane” na CPU w dowolnym momencie. Tak więc nie można uzyskać większej wydajności niż 8 wątków działających równolegle. W rzeczywistości wydajność prawdopodobnie spadnie wraz ze wzrostem liczby wątków, z powodu pracy związanej z tworzeniem, niszczeniem i zarządzaniem wątkami.

Wątki mogą znajdować się w różnych stanach: http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.State.html Spośród tych stanów, wątki RUNNABLE oznaczają fragment czasu procesora. System operacyjny decyduje o przydzieleniu czasu procesora na wątki. W zwykłym systemie z tysiącami wątków może być całkowicie nieprzewidywalny, gdy pewien wątek otrzyma czas procesora i czas jego działania na procesorze.

O problemie jesteś rozwiązywania:

Wydaje się, że zorientowali się właściwe rozwiązanie - wykonanie równoległe asynchroniczne żądania sieciowe. Jednak praktycznie uruchomienie 10000 wątków i wielu połączeń sieciowych w tym samym czasie może być obciążeniem dla zasobów systemowych i może po prostu nie działać. Ten post ma wiele sugestii dla asynchronicznego I/O przy użyciu Java. (Podpowiedź: nie popatrz tylko na zaakceptowaną odpowiedź)

0

To rozwiązanie jest bardziej specyficzne dla ogólnego problemu, który polega na próbie jak najszybszego wykonania żądań 10k. Sugerowałbym, żebyś porzucił biblioteki Java HTTP i zamiast tego używał Apache'a HttpClient. Mają kilka suggestions, aby zmaksymalizować wydajność, która może być przydatna. Słyszałem, że biblioteka Apache HttpClient jest po prostu szybsza, lżejsza i mniejsza narzut.