Jak działają wątki w Pythonie i jakie są typowe pułapki związane z wątkami w Pythonie?

85

Próbowałem pojąć, jak działają wątki w Pythonie i ciężko jest znaleźć dobre informacje na temat ich działania. Może po prostu brakuje mi linku lub czegoś takiego, ale wygląda na to, że oficjalna dokumentacja nie jest zbyt dokładna w tym temacie i nie udało mi się znaleźć dobrego artykułu.

Z tego, co wiem, na raz może działać tylko jeden wątek, a aktywny wątek przełącza się co około 10 instrukcji?

Gdzie jest dobre wyjaśnienie lub czy możesz je podać? Byłoby również bardzo miło być świadomym typowych problemów, które napotykasz podczas używania wątków w Pythonie.

jdd
źródło

Odpowiedzi:

50

Tak, ze względu na Global Interpreter Lock (GIL) w danym momencie można uruchomić tylko jeden wątek. Oto kilka linków z pewnymi spostrzeżeniami na ten temat:

Z ostatniego linku ciekawy cytat:

Pozwól mi wyjaśnić, co to wszystko oznacza. Wątki działają na tej samej maszynie wirtualnej, a zatem działają na tej samej maszynie fizycznej. Procesy mogą działać na tym samym komputerze fizycznym lub na innym komputerze fizycznym. Jeśli tworzysz swoją aplikację wokół wątków, nie robisz nic, aby uzyskać dostęp do wielu maszyn. Możesz więc skalować do tylu rdzeni na pojedynczej maszynie (co z czasem będzie całkiem sporo), ale aby naprawdę osiągnąć skalę sieciową, i tak będziesz musiał rozwiązać problem wielu maszyn.

Jeśli chcesz korzystać z wielu rdzeni, pyprocessing definiuje interfejs API oparty na procesach, aby wykonywać rzeczywistą równoległość. PEP zawiera również kilka interesujących punktów odniesienia.

Peter Hoffmann
źródło
1
Naprawdę komentarz do cytatu smoothspan: z pewnością wątki w Pythonie skutecznie ograniczają cię do jednego rdzenia, nawet jeśli maszyna ma kilka? Wiele rdzeni może przynieść korzyści, ponieważ następny wątek może być gotowy do pracy bez przełączania kontekstu, ale Twoje wątki Pythona nigdy nie mogą korzystać z> 1 rdzenia naraz.
James Brady
2
Prawda, wątki Pythona są praktycznie ograniczone do jednego rdzenia, chyba że moduł C ładnie współdziała z GIL i uruchamia swój własny natywny wątek.
Arafangion
W rzeczywistości wiele rdzeni sprawia, że ​​wątki są mniej wydajne, ponieważ podczas sprawdzania, czy każdy wątek ma dostęp do GIL, występuje dużo zmian. Nawet z nowym GIL wydajność jest jeszcze gorsza ... dabeaz.com/python/NewGIL.pdf
Basic
2
Należy pamiętać, że względy GIL nie dotyczą wszystkich tłumaczy. O ile mi wiadomo, zarówno IronPython, jak i Jython działają bez GIL, umożliwiając ich kodowi bardziej efektywne wykorzystanie sprzętu wieloprocesorowego. Jak wspomniał Arafangion, interpreter CPythona może również działać poprawnie wielowątkowo, jeśli kod, który nie potrzebuje dostępu do elementów danych Pythona, zwalnia blokadę, a następnie uzyskuje ją ponownie przed powrotem.
holdenweb
Co powoduje przełączanie kontekstu między wątkami w Pythonie? Czy opiera się na przerwaniach timera? Blokowanie czy konkretne wezwanie do zysku?
CMCDragonkai
36

Python jest dość łatwym językiem do wkręcenia, ale są pewne zastrzeżenia. Największą rzeczą, o której musisz wiedzieć, jest Global Interpreter Lock. Pozwala to tylko jednemu wątkowi na dostęp do interpretera. Oznacza to dwie rzeczy: 1) rzadko zdarza się, że używasz instrukcji lock w Pythonie i 2) jeśli chcesz skorzystać z systemów wieloprocesorowych, musisz użyć oddzielnych procesów. EDYCJA: Powinienem również zaznaczyć, że możesz umieścić część kodu w C / C ++, jeśli chcesz również ominąć GIL.

Dlatego musisz ponownie rozważyć, dlaczego chcesz używać wątków. Jeśli chcesz zrównoleglać swoją aplikację, aby wykorzystać architekturę dwurdzeniową, musisz rozważyć podzielenie aplikacji na wiele procesów.

Jeśli chcesz poprawić responsywność, powinieneś ROZWAŻYĆ użycie wątków. Istnieją jednak inne alternatywy, a mianowicie mikrowłókno . Jest też kilka frameworków, którym powinieneś się przyjrzeć:

Jason Baker
źródło
@JS - Naprawiono. Ta lista i tak była nieaktualna.
Jason Baker
Po prostu czuję się źle, że aby skorzystać z systemu wielordzeniowego, potrzebujesz wielu procesów - z całym tym narzutem. Mamy kilka serwerów z 32 rdzeniami logicznymi - więc potrzebuję 32 procesów, aby efektywnie z nich korzystać? Madness
Basic
@Basic - Obecnie narzut związany z rozpoczęciem procesu w porównaniu z rozpoczęciem wątku jest minimalny. Przypuszczam, że możesz zacząć widzieć problemy, jeśli mówimy o tysiącach zapytań na sekundę, ale w pierwszej kolejności kwestionowałbym wybór Pythona dla tak obciążonej usługi.
Jason Baker
20

Poniżej znajduje się podstawowa próbka gwintowania. Spowoduje 20 wątków; każdy wątek wyświetli swój numer wątku. Uruchom go i obserwuj kolejność drukowania.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Jak już zasugerowałeś, wątki Pythona są implementowane poprzez dzielenie czasu. W ten sposób uzyskują efekt „równoległości”.

W moim przykładzie moja klasa Foo rozszerza wątek, a następnie implementuję runmetodę, do której trafia kod, który chcesz uruchomić w wątku. Aby uruchomić wątek, który wywołujesz start()na obiekcie wątku, który automatycznie wywoła runmetodę ...

Oczywiście to tylko podstawy. W końcu będziesz chciał dowiedzieć się o semaforach, muteksach i blokadach do synchronizacji wątków i przekazywania wiadomości.

mmattax
źródło
10

Użyj wątków w języku Python, jeśli poszczególni pracownicy wykonują operacje powiązane we / wy. Jeśli próbujesz skalować na wielu rdzeniach na komputerze, znajdź dobrą strukturę IPC dla Pythona lub wybierz inny język.

Ben McNiel
źródło
4

Uwaga: wszędzie tam, gdzie wspominam, threadmam na myśli wątki w Pythonie, dopóki nie zostanie to wyraźnie określone.

Wątki działają trochę inaczej w Pythonie, jeśli przechodzisz z C/C++tła. W Pythonie tylko jeden wątek może być uruchomiony w danym momencie, co oznacza, że ​​wątki w Pythonie nie mogą w pełni wykorzystać mocy wielu rdzeni przetwarzających, ponieważ z założenia nie jest możliwe, aby wątki działały równolegle na wielu rdzeniach.

Ponieważ zarządzanie pamięcią w Pythonie nie jest bezpieczne dla wątków, każdy wątek wymaga wyłącznego dostępu do struktur danych w interpreterze Pythona, który jest uzyskiwany przez mechanizm o nazwie (globalna blokada interpretr) .GIL

Why does python use GIL?

Aby zapobiec jednoczesnemu dostępowi wielu wątków do stanu interpretera i uszkodzeniu stanu interpretera.

Chodzi o to, że za każdym razem, gdy wątek jest wykonywany (nawet jeśli jest to główny wątek) , uzyskiwany jest GIL i po pewnym predefiniowanym przedziale czasu GIL jest zwalniany przez bieżący wątek i ponownie pobierany przez inny wątek (jeśli istnieje).

Why not simply remove GIL?

Nie jest tak, że niemożliwe jest usunięcie GIL-a, po prostu w trakcie wykonywania tego w końcu umieszczamy wiele blokad wewnątrz interpretera w celu serializacji dostępu, co sprawia, że ​​nawet pojedyncza aplikacja wątkowa jest mniej wydajna.

więc koszt usunięcia GIL jest opłacany przez zmniejszoną wydajność aplikacji jednowątkowej, co nigdy nie jest pożądane.

So when does thread switching occurs in python?

Zmiana wątku następuje po wydaniu GIL. Więc kiedy GIL zostanie wydany? Należy wziąć pod uwagę dwa scenariusze.

Jeśli wątek wykonuje operacje związane z procesorem (przetwarzanie obrazu Ex).

W starszych wersjach Pythona przełączanie wątków występowało po ustalonej liczbie instrukcji Pythona, domyślnie było ustawione na 100. Okazało się, że nie jest to zbyt dobra polityka decydowania o przełączeniu, ponieważ czas spędzony na wykonywaniu pojedynczej instrukcji może bardzo szalenie od milisekundy do nawet sekundy, dlatego zwalnianie GIL po każdej 100instrukcji, niezależnie od czasu jej wykonania, jest złą polityką.

W nowych wersjach zamiast używania liczby instrukcji jako metryki przełączania wątku, używany jest konfigurowalny przedział czasu. Domyślny interwał przełączania to 5 milisekund. Bieżący interwał przełączania można uzyskać za pomocą sys.getswitchinterval(). Można to zmienić za pomocąsys.setswitchinterval()

Jeśli wątek wykonuje operacje związane z operacjami we / wy (dostęp do systemu plików Ex lub we /
wy sieci)

GIL jest wydawany, gdy wątek czeka na zakończenie operacji we / wy.

Which thread to switch to next?

Interpreter nie ma własnego harmonogramu, a który wątek zostanie zaplanowany na koniec interwału, jest decyzją systemu operacyjnego. .

anekix
źródło
3

Jednym prostym rozwiązaniem GIL jest moduł wieloprocesowy . Może być używany jako kropla w zastępstwie modułu wątkowego, ale używa wielu procesów interpretera zamiast wątków. Z tego powodu w przypadku prostych rzeczy jest trochę więcej narzutów niż zwykłe gwintowanie, ale daje to korzyść prawdziwej równoległości, jeśli jej potrzebujesz. Można go również łatwo skalować do wielu fizycznych komputerów.

Jeśli potrzebujesz naprawdę wielkoskalowej równoległości, nie szukałbym dalej, ale jeśli chcesz po prostu skalować do wszystkich rdzeni jednego komputera lub kilku różnych bez całej pracy, która wymagałaby wdrożenia bardziej kompleksowej struktury, to jest to dla ciebie .

Czy t
źródło
2

Spróbuj zapamiętać, że GIL jest ustawiony tak często, aby sondować wokół, aby pokazać wygląd wielu zadań. To ustawienie można precyzyjnie dostroić, ale sugeruję, że powinno być trochę pracy, którą wykonują wątki lub wiele przełączników kontekstu spowoduje problemy.

Posunąłbym się do tego, że zasugerowałbym wielu rodziców na procesorach i starałbym się utrzymać podobne zadania na tych samych rdzeniach.

phreaki
źródło