decydujesz się na podproces, wieloprocesowość i wątek w Pythonie?

110

Chciałbym zsynchronizować mój program w Pythonie, aby mógł korzystać z wielu procesorów na maszynie, na której działa. Moja równoległość jest bardzo prosta, ponieważ wszystkie równoległe „wątki” programu są niezależne i zapisują swoje dane wyjściowe w oddzielnych plikach. Nie potrzebuję wątków do wymiany informacji, ale konieczne jest, aby wiedzieć, kiedy wątki się skończą, ponieważ niektóre etapy mojego potoku zależą od ich danych wyjściowych.

Przenośność jest ważna, ponieważ chciałbym, aby działała na dowolnej wersji Pythona na komputerach Mac, Linux i Windows. Biorąc pod uwagę te ograniczenia, który z modułów Pythona jest najbardziej odpowiedni do zaimplementowania tego? Próbuję wybrać między wątkiem, podprocesem i wieloprocesorem, które wydają się zapewniać pokrewną funkcjonalność.

Jakieś przemyślenia na ten temat? Chciałbym najprostsze przenośne rozwiązanie.

Vaibhav Mule
źródło
Powiązane: stackoverflow.com/questions/1743293/ ... (przeczytaj moją odpowiedź, aby zobaczyć, dlaczego wątki nie są uruchamiane dla kodu w czystym Pythonie)
1
„Dowolna wersja Pythona” jest ZNACZNIE niejasna. Python 2.3? 1.x? 3.x? Jest to po prostu niemożliwy do spełnienia warunek.
ostrożnie,

Odpowiedzi:

64

multiprocessingto świetny moduł typu szwajcarskiego scyzoryka. Jest bardziej ogólny niż wątki, ponieważ można nawet wykonywać zdalne obliczenia. Dlatego jest to moduł, który sugerowałbym, abyś użył.

subprocessModuł będzie również pozwalają na uruchomienie wielu procesów, ale okazało się, że jest mniej wygodny w użyciu niż nowego modułu wieloprocesorowej.

Wątki są notorycznie subtelne, a dzięki CPython często jesteś ograniczony do jednego rdzenia, z nimi (chociaż, jak zauważono w jednym z komentarzy, Global Interpreter Lock (GIL) może być zwolniony w kodzie C wywołanym z kodu Pythona) .

Uważam, że większość funkcji wymienionych trzech modułów może być wykorzystywana w sposób niezależny od platformy. Jeśli chodzi o przenośność, zwróć uwagę, że multiprocessingwystępuje w standardzie tylko od Pythona 2.6 (choć istnieje wersja dla niektórych starszych wersji Pythona). Ale to świetny moduł!

Eric O Lebigot
źródło
1
do przypisania użyłem właśnie modułu "multiprocessing" i jego metody pool.map (). bułka z masłem !
kmonsoor
Czy rozważa się też coś takiego jak seler? Dlaczego tak jest, czy nie?
user3245268
O ile wiem, Celery jest bardziej zaangażowany (musisz zainstalować jakiegoś brokera wiadomości), ale jest to opcja, którą prawdopodobnie należy rozważyć, w zależności od problemu.
Eric O Lebigot
186

Dla mnie jest to całkiem proste:

Opcja podprocesu :

subprocesssłuży do uruchamiania innych plików wykonywalnych - jest to w zasadzie opakowanie dookoła os.fork()i os.execve()z pewną obsługą opcjonalnej instalacji wodociągowej (konfigurowanie PIPE do i z podprocesów. Oczywiście można też zastosować inne mechanizmy komunikacji międzyprocesowej (IPC), takie jak gniazda, Posix lub Pamięć współdzielona SysV, ale będziesz ograniczony do interfejsów i kanałów IPC obsługiwanych przez programy, które wywołujesz.

Zwykle używa się dowolnego narzędzia subprocesssynchronicznie - po prostu wywołując jakieś zewnętrzne narzędzie i odczytując jego dane wyjściowe lub czekając na jego zakończenie (być może czytając jego wyniki z pliku tymczasowego lub po wysłaniu ich do jakiejś bazy danych).

Jednak można stworzyć setki podprocesów i sondować je. Moja ulubiona klasa narzędziowa właśnie to robi. Największą wadą tego subprocessmodułu jest to, że wsparcie I / O jest zazwyczaj blokując. Istnieje szkic PEP-3145, który naprawi ten problem w niektórych przyszłych wersjach Pythona 3.xi alternatywny asyncproc (ostrzeżenie, które prowadzi prosto do pobrania, a nie do jakiejkolwiek dokumentacji ani pliku README). Odkryłem również, że stosunkowo łatwo jest po prostu zaimportować fcntli bezpośrednio manipulować Popendeskryptorami plików PIPE - chociaż nie wiem, czy jest to przenośne na platformy inne niż UNIX.

(Aktualizacja: 7 sierpnia 2019: obsługa Python 3 dla podprocesów ayncio : podprocesy asyncio )

subprocess prawie nie obsługuje obsługi zdarzeń ... chociaż możesz użyć signalmodułu i zwykłych sygnałów ze starej szkoły UNIX / Linux - łagodnie zabijając procesy.

Opcja przetwarzania wieloprocesowego :

multiprocessingsłuży do uruchamiania funkcji w istniejącym kodzie (Python) z obsługą bardziej elastycznej komunikacji między tą rodziną procesów. W szczególności najlepiej jest budować swój multiprocessingIPC wokół Queueobiektów modułu, jeśli to możliwe, ale możesz także używać Eventobiektów i różnych innych funkcji (z których niektóre są prawdopodobnie zbudowane wokół mmapwsparcia na platformach, na których ta obsługa jest wystarczająca).

multiprocessingModuł Pythona ma zapewniać interfejsy i funkcje, które są bardzo podobne do, threading jednocześnie umożliwiając CPythonowi skalowanie przetwarzania między wieloma procesorami / rdzeniami pomimo GIL (Global Interpreter Lock). Wykorzystuje wszystkie drobnoziarniste blokowanie SMP i wysiłek związany z koherencją, który został wykonany przez programistów jądra twojego systemu operacyjnego.

Opcja gwintowania :

threadingjest przeznaczony do dość wąskiego zakresu aplikacji, które są związane z operacjami we / wy (nie wymagają skalowania na wiele rdzeni procesora) i które korzystają z wyjątkowo niskiego opóźnienia i narzutu przełączania przełączania wątków (ze współdzieloną pamięcią rdzeniową) w porównaniu z procesem / przełączanie kontekstu. W systemie Linux jest to prawie pusty zestaw (czasy przełączania procesów w systemie Linux są bardzo zbliżone do przełączników wątków).

threadingma dwie główne wady w Pythonie .

Jeden, oczywiście, jest specyficzny dla implementacji - głównie dotyczy CPythona. To jest GIL. W przeważającej części, większość programów CPython nie skorzystają z dostępności więcej niż dwa procesory (rdzenie) i często wydajność będzie cierpieć z niezgody blokującego GIL.

Większy problem, który nie jest specyficzny dla implementacji, polega na tym, że wątki współużytkują tę samą pamięć, programy obsługi sygnałów, deskryptory plików i niektóre inne zasoby systemu operacyjnego. Dlatego programista musi bardzo uważać na blokowanie obiektów, obsługę wyjątków i inne aspekty swojego kodu, które są zarówno subtelne, jak i mogą zabić, zablokować lub zablokować cały proces (zestaw wątków).

Dla porównania multiprocessingmodel nadaje każdemu procesowi własną pamięć, deskryptory plików itp. Awaria lub nieobsługiwany wyjątek w którymkolwiek z nich zabije tylko ten zasób, a solidna obsługa zniknięcia procesu potomnego lub rodzeństwa może być znacznie łatwiejsza niż debugowanie, izolowanie oraz naprawianie lub obejście podobnych problemów w wątkach.

  • (Uwaga: używanie programu threadingz głównymi systemami Pythona, takimi jak NumPy , może znacznie mniej cierpieć z powodu rywalizacji GIL niż większość własnego kodu w Pythonie. Dzieje się tak, ponieważ zostały specjalnie zaprojektowane do tego; natywne / binarne części NumPy, na przykład zwolni GIL, gdy będzie to bezpieczne).

Twisted opcja:

Warto również zauważyć, że Twisted oferuje kolejną alternatywę, która jest zarówno elegancka, jak i bardzo trudna do zrozumienia . Zasadniczo, ryzykując zbytnie uproszczenie do punktu, w którym fani Twisted mogą szturmować mój dom z widłami i pochodniami, Twisted zapewnia współpracę wielozadaniową opartą na zdarzeniach w ramach dowolnego (pojedynczego) procesu.

Aby zrozumieć, jak to jest możliwe, powinno się przeczytać o funkcjach select()(które mogą być budowane wokół select () lub poll () lub podobnych wywołań systemowych OS). Zasadniczo wszystko jest napędzane możliwością wysłania żądania uśpienia systemu operacyjnego w oczekiwaniu na jakąkolwiek aktywność na liście deskryptorów plików lub przekroczenia limitu czasu.

Przebudzenie z każdego z tych wywołań select()jest zdarzeniem - albo takim, w którym dane wejściowe są dostępne (czytelne) w pewnej liczbie gniazd lub deskryptorów plików, albo przestrzeń buforowa staje się dostępna w innych (zapisywalnych) deskryptorach lub gniazdach, niektóre wyjątkowe warunki (TCP na przykład pozapasmowe pakiety PUSH) lub TIMEOUT.

Tak więc model programowania Twisted jest zbudowany wokół obsługi tych zdarzeń, a następnie zapętlony na wynikowym „głównym” module obsługi, umożliwiając mu wysyłanie zdarzeń do twoich programów obsługi.

Osobiście myślę o nazwie Twisted jako kojarzącej się z modelem programowania ... ponieważ twoje podejście do problemu musi być w pewnym sensie „wypaczone” na lewą stronę. Zamiast wyobrażać sobie program jako serię operacji na danych wejściowych i wyjściach lub wynikach, piszesz program jako usługę lub demon i definiujesz, jak reaguje na różne zdarzenia. (W rzeczywistości podstawową "główną pętlą" programu Twisted jest (zwykle? Zawsze?) A reactor()).

Do najważniejszych zadań przy użyciu Twisted zaangażować swój umysł skręcania wokół modelu zdarzeniami, a także unikając użycia jakichkolwiek bibliotek klas lub zestawów narzędzi, które nie są napisane współpracować w Twisted ramy. Dlatego Twisted dostarcza własne moduły do ​​obsługi protokołu SSH, dla curses i własnych funkcji podprocesu / Popen, a także wiele innych modułów i programów obsługi protokołów, które na pierwszy rzut oka wydają się powielać rzeczy w standardowych bibliotekach Pythona.

Myślę, że warto zrozumieć Twisted na poziomie koncepcyjnym, nawet jeśli nigdy nie zamierzasz go używać. Może dać wgląd w wydajność, rywalizację i obsługę zdarzeń w twoich wątkach, przetwarzaniu wieloprocesowym, a nawet obsłudze podprocesów, a także w przetwarzaniu rozproszonym, które podejmujesz.

( Uwaga: Nowsze wersje Pythona 3.x są tym asyncio (asynchroniczne I / O) dysponuje takimi jak asynchroniczny def , w @ async.coroutine dekorator, i czekają na słowa kluczowe, a wydajnością z przyszłości obsługiwać wszystkie z nich są zbliżone do. Skręcone z perspektywy procesu (wielozadaniowość kooperacyjna)). (Aby uzyskać aktualny stan obsługi Twisted dla Pythona 3, sprawdź: https://twistedmatrix.com/documents/current/core/howto/python3.html )

Wersja dystrybuowana :

Kolejną dziedziną przetwarzania, o którą nie pytałeś, ale którą warto rozważyć, jest przetwarzanie rozproszone . Istnieje wiele narzędzi i struktur Pythona do przetwarzania rozproszonego i obliczeń równoległych. Osobiście uważam, że najłatwiejszy w użyciu jest taki, który jest najrzadziej uważany za znajdujący się w tej przestrzeni.

Tworzenie rozproszonego przetwarzania wokół Redis jest prawie trywialne . Cały magazyn kluczy może być używany do przechowywania jednostek pracy i wyników, LISTY Redis mogą być używane jako Queue()podobne obiekty, a obsługa PUB / SUB może być używana Eventdo obsługi podobnej do tej. Możesz haszować swoje klucze i używać wartości replikowanych w luźnym klastrze instancji Redis, aby przechowywać topologię i mapowania znaczników skrótu, aby zapewnić spójne mieszanie i przełączanie awaryjne w celu skalowania poza możliwości pojedynczego wystąpienia w celu koordynowania pracowników i organizowanie danych (piklowane, JSON, BSON lub YAML) wśród nich.

Oczywiście, jak zacząć budować większą skalę i bardziej wyrafinowane rozwiązania wokół Redis jesteś ponownego wykonania wiele funkcji, które zostały już rozwiązane za pomocą, Seler , Apache Spark i Hadoop , Zookeeper , etcd , Cassandra i tak dalej. Wszystkie mają moduły umożliwiające dostęp do swoich usług w języku Python.

[Aktualizacja: Kilka zasobów do rozważenia, jeśli rozważasz Python do intensywnego obliczania w systemach rozproszonych: IPython Parallel i PySpark . Chociaż są to rozproszone systemy obliczeniowe ogólnego przeznaczenia, są one szczególnie łatwo dostępnymi i popularnymi podsystemami nauki i analizy danych].

Wniosek

Masz do dyspozycji gamę alternatyw przetwarzania dla Pythona, od jednowątkowych, z prostymi synchronicznymi wywołaniami do podprocesów, pulami odpytywanych podprocesów, wielowątkową i wielowątkową, kooperatywną wielozadaniowością sterowaną zdarzeniami, aż po przetwarzanie rozproszone.

Jim Dennis
źródło
1
Trudno jest jednak używać przetwarzania wieloprocesowego z klasami / OOP.
Tjorriemorrie,
2
@Tjorriemorrie: Zgaduję, że masz na myśli, że trudno jest wysłać wywołania metod do instancji obiektów, które mogą znajdować się w innych procesach. Sugerowałbym, że jest to ten sam problem, który miałbyś z wątkami, ale bardziej widoczny (zamiast być kruchym i podlegającym niejasnym warunkom wyścigu). Myślę, że zalecanym podejściem byłoby zorganizowanie wszystkich takich wysyłek za pośrednictwem obiektów kolejki, które działają jednowątkowo, wielowątkowo i między procesami. (Z niektórymi implementacjami Redis lub Celery Queue, nawet w klastrze węzłów)
Jim Dennis
2
To naprawdę dobra odpowiedź. Chciałbym, żeby było to we wprowadzeniu do współbieżności w dokumentacji Python3.
root-11
1
@ root-11, możesz zaproponować to opiekunom dokumentów; Opublikowałem go tutaj do bezpłatnego użytku. Zapraszamy do korzystania z niego w całości lub w częściach.
Jim Dennis,
„Dla mnie jest to całkiem proste:„ Uwielbiam to. wielkie dzięki
jerome
5

W podobnym przypadku zdecydowałem się na oddzielne procesy i odrobinę niezbędnej komunikacji przez gniazdo sieciowe. Jest bardzo przenośny i dość prosty do wykonania przy użyciu Pythona, ale prawdopodobnie nie prostszy (w moim przypadku miałem też inne ograniczenie: komunikację z innymi procesami napisanymi w C ++).

W twoim przypadku prawdopodobnie wybrałbym proces wieloprocesowy, ponieważ wątki Pythona, przynajmniej w przypadku korzystania z CPythona, nie są prawdziwymi wątkami. Cóż, są to natywne wątki systemowe, ale moduły C wywoływane z Pythona mogą, ale nie muszą, zwolnić GIL i pozwolić innym wątkom na uruchomienie ich podczas wywoływania kodu blokującego.

kriss
źródło
4

Aby używać wielu procesorów w CPythonie, jedynym wyborem jest multiprocessingmoduł. CPython blokuje swoje wewnętrzne elementy ( GIL ), co zapobiega równoległej pracy wątków na innych procesorach CPU. multiprocessingModuł tworzy nowe procesy (jak subprocess) i zarządza komunikacją między nimi.

Jochen Ritzel
źródło
5
To nie do końca prawda, AFAIK można zwolnić GIL za pomocą C API, a są inne implementacje Pythona, takie jak IronPython czy Jython, które nie cierpią z powodu takich ograniczeń. Jednak nie głosowałem przeciw.
Bastien Léonard
1

Wyłuskuj i pozwól unixowi wykonać twoje zadania:

użyj iterpipes, aby zawinąć podproces, a następnie:

Ze strony Teda Ziuby

INPUTS_FROM_YOU | xargs -n1 -0 -P LICZ ./proces #LICZBA równoległych procesów

LUB

Równoległy Gnu również posłuży

Spędzasz czas z GIL, wysyłając chłopaków z zaplecza, aby wykonali twoją pracę wielordzeniową.

chiggsy
źródło
6
„Przenośność jest ważna, ponieważ chciałbym, aby działała na dowolnej wersji Pythona na komputerach Mac, Linux i Windows”.
ostrożnie,
Czy dzięki temu rozwiązaniu możesz wielokrotnie wchodzić w interakcje z pracą? Możesz to zrobić w przetwarzaniu wieloprocesowym, ale nie sądzę w podprocesie.
abalter