2009-07-02 15 views
5

Próbuję utworzyć usługę, która zawiera wiele gniazd klientów i serwerów (usługa serwera, a także klientów, które łączą się z zarządzanymi komponentami i są przechowywane), które są synchronicznie odpytywane przez IO::Select. Pomysł polegał na obsłużeniu operacji we/wy i/lub żądania przetwarzania, które powstają w wyniku puli wątków roboczych.Czy niezablokowane operacje we/wy w Perlu są ograniczone do jednego wątku? Dobry projekt?

Słowo kluczowe, które umożliwia współdzielenie danych przez wątki w Perlu (threads::shared) ma swoje ograniczenia - odniesienia do uchwytów nie należą do prymitywów, które można udostępnić.

Zanim zorientowali się, że uchwyty i/lub uchwyt referencje nie mogą być udostępniane, w planie było mieć select() wątek, który dba o odpytywania, a następnie umieszcza odpowiednie uchwyty w pewnym ThreadQueue y rozłożone puli gwintu właściwie robię czytanie i pisanie. (Oczywiście projektowałem to tak, aby modyfikacja rzeczywistych zestawów deskryptorów używanych przez select była bezpieczna dla wątków i odbywała się tylko w jednym wątku - tym samym, który działa select(), a więc nigdy podczas jego działania, oczywiście.)

To nie wygląda na to, że stanie się to teraz, ponieważ same uchwyty nie mogą być udostępniane, więc zarówno głosowanie, jak i czytanie i pisanie będą musiały się zdarzyć z jednego wątku. Czy istnieje jakieś obejście tego problemu? Mam na myśli rozkład rzeczywistych wywołań systemowych przez wątki; są oczywiście sposoby na wykorzystanie kolejek i buforów, aby uzyskać dane wytworzone w innych wątkach i faktycznie wysłane w innych.

Jednym z problemów, które wynikają z tej sytuacji jest to, że muszę dać select() limit czasu i oczekiwać, że będzie wystarczająco wysoka, aby nie powodować problemów z odpytywaniem dość dużego zestawu deskryptorów, a jednocześnie wystarczająco niska, aby nie wprowadzić dużo opóźnień w pętli zdarzeń czasowych - chociaż rozumiem, że jeśli w procesie odpytywania wykryte zostanie rzeczywiste członkostwo we/wy, select() wróci wcześniej, co częściowo złagodzi problem. Wolałbym mieć jakiś sposób na obudzenie się z innego wątku, ale ponieważ uchwytów nie można udostępnić, nie mogę łatwo wymyślić sposobu na zrobienie tego, ani zobaczyć korzyści z tego; O czym jest inny wątek, o którym warto wiedzieć, kiedy należy obudzić się na select()?

Jeśli nie można obejść tego problemu, jaki jest dobry wzór projektu dla tego typu usługi w języku Perl? Mam wymaganie dość dużej skalowalności i współbieżnych operacji wejścia/wyjścia, i z tego powodu przeszedłem ścieżkę bez blokowania, a nie tylko odradzam wątki dla każdego gniazda nasłuchu i/lub procesu klienta i/lub serwera, jak wielu ludzi używających języki pozycyjne w dzisiejszych czasach będą działać w przypadku gniazd - wydaje się, że jest to typowa praktyka w krainie Jawy i nikt nie wydaje się być zainteresowany poza wąskim obszarem programowania zorientowanego na systemy. Może to tylko moje wrażenie. W każdym razie nie chcę tego robić w ten sposób.

A więc, z punktu widzenia doświadczonego programisty systemów Perla, w jaki sposób powinno się to zorganizować? Monolityczny wątek we/wy + wątki czystego pracownika (inne niż I/O) + wiele kolejek? Jakiś sprytny hack? Jakieś wątki bezpieczeństwa, które wychodzą poza to, co już wyliczyłem? Czy istnieje lepsza droga? Mam duże doświadczenie w projektowaniu tego typu programów w języku C, ale nie przy użyciu idiomów języka Perl i cech środowiska wykonawczego.

EDYCJA: P.S. Zdecydowanie przyszło mi do głowy, że być może program z tymi wymaganiami wydajnościowymi i tym projektem powinien po prostu nie być napisany w Perlu. Ale widzę strasznie dużo bardzo wyrafinowanych usług produkowanych w Perlu, więc nie jestem tego pewien.

Odpowiedz

5

Bracketing się z kilku, większych pytań projektowych, mogę zaoferować kilka sposobów udostępniania uchwytów plików w wątkach perl.

Jeden może przekazać $client do wątku rozpocząć rutynowe lub po prostu odwołać go w nowym wątku:

$client = $server_socket->accept(); 

threads->new(\&handle_client, $client); 
async { handle_client($client) }; 
# $client will be closed only when all threads' references 
# to it pass out of scope. 

Dla Thread::Queue projektu, można enqueue() fd podstawowych:

$q->enqueue(POSIX::dup(fileno $client)); 
# we dup(2) so that $client may safely go out of scope, 
# closing its underlying fd but not the duplicate thereof 

async { 
    my $client = IO::Handle->new_from_fd($q->dequeue, "r+"); 
    handle_client($client); 
}; 

lub jeden może po prostu używać wyłącznie fds, a forma wektorowa bitów Perla to select.

+0

Interesujące sugestie. Szczególnie podoba mi się podejście polegające na przejściu leżącego poniżej FD, a następnie zbudowaniu z niego uchwytu później. Czy możesz zaproponować sposób inicjalizacji uchwytu pliku, a następnie przypisać go do IO :: Socket później, w wątku? Wolałbym nie tworzyć gniazd w jednym wątku, a następnie manipulować nimi w innym; czy można zrobić coś typowego, jak IO :: Handle-> new? –

+0

Alex, bez pseudokodu, nie jestem pewien, czy rozumiem szczegóły twojego pytania tutaj. Jednak użycie "IO :: Socket :: INET" i "IO :: Socket :: INET-> new_from_fd ($ my_duplicated_fd," r + ")" spowoduje pobranie obiektu IO :: Socket. – pilcrow

+0

Och, nie zdawałem sobie sprawy, że IO :: Socket :: INET jest podklasą IO :: Handle. Ma sens. –