Wątkowanie w Pythonie: wprowadzenie – newsblog.pl

W tym samouczku dowiesz się, jak korzystać z wbudowanego modułu wątkowości Pythona do odkrywania możliwości wielowątkowości w Pythonie.

Zaczynając od podstaw procesów i wątków, dowiesz się, jak działa wielowątkowość w Pythonie — jednocześnie rozumiejąc koncepcje współbieżności i równoległości. Następnie dowiesz się, jak uruchomić i uruchomić jeden lub więcej wątków w Pythonie za pomocą wbudowanego modułu wątków.

Zacznijmy.

Procesy a wątki: jakie są różnice?

Czym jest proces?

Proces to dowolna instancja programu, który musi zostać uruchomiony.

Może to być cokolwiek – skrypt Pythona lub przeglądarka internetowa, taka jak Chrome, po aplikację do wideokonferencji. Jeśli uruchomisz Menedżera zadań na swoim komputerze i przejdziesz do Wydajność -> Procesor, będziesz mógł zobaczyć procesy i wątki, które są aktualnie uruchomione na rdzeniach Twojego procesora.

Zrozumienie procesów i wątków

Wewnętrznie proces ma dedykowaną pamięć, która przechowuje kod i dane odpowiadające procesowi.

Proces składa się z co najmniej jednego wątku. Wątek to najmniejsza sekwencja instrukcji, które system operacyjny może wykonać i reprezentuje przepływ wykonywania.

Każdy wątek ma swój własny stos i rejestry, ale nie ma dedykowanej pamięci. Wszystkie wątki powiązane z procesem mogą uzyskać dostęp do danych. Dlatego dane i pamięć są współdzielone przez wszystkie wątki procesu.

W procesorze z N rdzeniami N procesów może być wykonywanych równolegle w tym samym czasie. Jednak dwa wątki tego samego procesu nigdy nie mogą być wykonywane równolegle, ale mogą być wykonywane jednocześnie. W następnej sekcji zajmiemy się pojęciem współbieżności i równoległości.

Bazując na tym, czego się do tej pory dowiedzieliśmy, podsumujmy różnice między procesem a wątkiem.

CechaProcessWątekPamięćPamięć dedykowanaPamięć współdzielonaTryb wykonaniaRównoległy, współbieżnyWspółbieżny; ale nierównoległeWykonywanie obsługiwane przez system operacyjnyInterpreter CPython

Wielowątkowość w Pythonie

W Pythonie Global Interpreter Lock (GIL) zapewnia, że ​​tylko jeden wątek może uzyskać blokadę i działać w dowolnym momencie. Wszystkie wątki powinny uzyskać tę blokadę do uruchomienia. Gwarantuje to, że tylko jeden wątek może być wykonywany — w dowolnym momencie — i pozwala uniknąć jednoczesnej wielowątkowości.

Rozważmy na przykład dwa wątki, t1 i t2, tego samego procesu. Ponieważ wątki współdzielą te same dane, gdy t1 odczytuje określoną wartość k, t2 może modyfikować tę samą wartość k. Może to prowadzić do zakleszczeń i niepożądanych wyników. Ale tylko jeden z wątków może uzyskać blokadę i działać w dowolnym wystąpieniu. Dlatego GIL zapewnia również bezpieczeństwo gwintów.

Jak więc osiągnąć wielowątkowość w Pythonie? Aby to zrozumieć, omówmy koncepcje współbieżności i równoległości.

Współbieżność a równoległość: przegląd

Rozważ procesor z więcej niż jednym rdzeniem. Na poniższej ilustracji procesor ma cztery rdzenie. Oznacza to, że w dowolnym momencie możemy wykonywać równolegle cztery różne operacje.

Jeśli są cztery procesy, to każdy z nich może działać niezależnie i jednocześnie na każdym z czterech rdzeni. Załóżmy, że każdy proces ma dwa wątki.

Aby zrozumieć, jak działa wątkowość, przejdźmy z architektury wielordzeniowej do jednordzeniowej. Jak wspomniano, tylko jeden wątek może być aktywny w określonej instancji wykonania; ale rdzeń procesora może przełączać się między wątkami.

Na przykład wątki związane z we/wy często czekają na operacje we/wy: odczytywanie danych wejściowych użytkownika, odczyty bazy danych i operacje na plikach. Podczas tego czasu oczekiwania może zwolnić blokadę, aby drugi wątek mógł działać. Czas oczekiwania może być również prostą operacją, taką jak spanie przez n sekund.

Podsumowując: Podczas operacji oczekiwania wątek zwalnia blokadę, umożliwiając przełączenie rdzenia procesora na inny wątek. Wcześniejszy wątek wznawia wykonywanie po zakończeniu okresu oczekiwania. Ten proces, w którym rdzeń procesora jednocześnie przełącza się między wątkami, ułatwia wielowątkowość.

Jeśli chcesz zaimplementować równoległość na poziomie procesu w swojej aplikacji, rozważ zamiast tego użycie wieloprocesorowości.

Moduł wątkowości w Pythonie: pierwsze kroki

Python jest dostarczany z modułem wątków, który można zaimportować do skryptu Pythona.

import threading

Aby utworzyć obiekt wątku w Pythonie, możesz użyć konstruktora Thread: threading.Thread(…). Jest to ogólna składnia, która wystarcza w przypadku większości implementacji wątków:

threading.Thread(target=...,args=...)

Tutaj,

  • cel jest argumentem słowa kluczowego oznaczającym wywoływalne w Pythonie
  • args jest krotką argumentów, które przyjmuje cel.

Będziesz potrzebować Pythona 3.x do uruchomienia przykładów kodu w tym samouczku. Pobierz kod i postępuj zgodnie z instrukcjami.

Jak definiować i uruchamiać wątki w Pythonie

Zdefiniujmy wątek, który uruchamia funkcję docelową.

Funkcja docelowa to some_func.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
print(threading.active_count())

Przeanalizujmy, co robi powyższy fragment kodu:

  • Importuje moduły wątków i czasu.
  • Funkcja some_func ma opisowe instrukcje print() i zawiera operację uśpienia na dwie sekundy: time.sleep(n) powoduje uśpienie funkcji na n sekund.
  • Następnie definiujemy wątek thread_1 z celem jako some_func. threading.Thread(target=…) tworzy obiekt wątku.
  • Uwaga: Określ nazwę funkcji, a nie wywołanie funkcji; użyj some_func, a nie some_func().
  • Tworzenie obiektu wątku nie rozpoczyna wątku; wywołuje metodę start() na obiekcie wątku.
  • Aby uzyskać liczbę aktywnych wątków, używamy funkcji active_count().

Skrypt Pythona działa w głównym wątku i tworzymy kolejny wątek (wątek1), aby uruchomić funkcję some_func, więc liczba aktywnych wątków wynosi dwa, jak widać na wyjściu:

# Output
Running some_func...
2
Finished running some_func.

Jeśli przyjrzymy się bliżej wynikowi, zobaczymy, że po uruchomieniu wątku1 uruchamiana jest pierwsza instrukcja print. Jednak podczas operacji uśpienia procesor przełącza się na wątek główny i drukuje liczbę aktywnych wątków — bez czekania na zakończenie wykonywania wątku1.

Oczekiwanie na zakończenie wykonywania wątków

Jeśli chcesz, aby thread1 zakończył wykonywanie, możesz wywołać na nim metodę join() po uruchomieniu wątku. Spowoduje to oczekiwanie na zakończenie wykonywania thread1 bez przełączania się do głównego wątku.

import threading
import time

def some_func():
    print("Running some_func...")
    time.sleep(2)
    print("Finished running some_func.")

thread1 = threading.Thread(target=some_func)
thread1.start()
thread1.join()
print(threading.active_count())

Teraz thread1 zakończył wykonywanie, zanim wydrukujemy liczbę aktywnych wątków. Tak więc działa tylko główny wątek, co oznacza, że ​​liczba aktywnych wątków wynosi jeden.

# Output
Running some_func...
Finished running some_func.
1

Jak uruchomić wiele wątków w Pythonie

Następnie utwórzmy dwa wątki, aby uruchomić dwie różne funkcje.

Tutaj count_down jest funkcją, która przyjmuje liczbę jako argument i odlicza od tej liczby do zera.

def count_down(n):
    for i in range(n,-1,-1):
        print(i)

Definiujemy count_up, kolejną funkcję Pythona, która liczy od zera do podanej liczby.

def count_up(n):
    for i in range(n+1):
        print(i)

📑 W przypadku korzystania z funkcji range() ze składnią range (start, stop, step) domyślnie wykluczone jest zatrzymanie punktu końcowego.

– Aby odliczać od określonej liczby do zera, można użyć ujemnej wartości kroku równej -1 i ustawić wartość zatrzymania na -1, aby uwzględnić zero.

– Podobnie, aby liczyć do n, musisz ustawić wartość stopu na n + 1. Ponieważ domyślne wartości start i step to odpowiednio 0 i 1, możesz użyć range(n + 1), aby uzyskać sekwencję 0 do n.

Następnie definiujemy dwa wątki, thread1 i thread2, aby uruchomić odpowiednio funkcje count_down i count_up. Do obu funkcji dodajemy instrukcje drukowania i operacje uśpienia.

Podczas tworzenia obiektów wątków zwróć uwagę, że argumenty funkcji docelowej powinny być określone jako krotka — do parametru args. Ponieważ obie funkcje (count_down i count_up) przyjmują jeden argument. Będziesz musiał wyraźnie wstawić przecinek po wartości. Zapewnia to, że argument jest nadal przekazywany jako krotka, ponieważ kolejne elementy są wywnioskowane jako None.

import threading
import time

def count_down(n):
    for i in range(n,-1,-1):
        print("Running thread1....")
        print(i)
        time.sleep(1)


def count_up(n):
    for i in range(n+1):
        print("Running thread2...")
        print(i)
        time.sleep(1)

thread1 = threading.Thread(target=count_down,args=(10,))
thread2 = threading.Thread(target=count_up,args=(5,))
thread1.start()
thread2.start()

Na wyjściu:

  • Funkcja count_up działa na thread2 i liczy do 5, zaczynając od 0.
  • Funkcja count_down działa na wątku1 odlicza od 10 do 0.
# Output
Running thread1....
10
Running thread2...
0
Running thread1....
9
Running thread2...
1
Running thread1....
8
Running thread2...
2
Running thread1....
7
Running thread2...
3
Running thread1....
6
Running thread2...
4
Running thread1....
5
Running thread2...
5
Running thread1....
4
Running thread1....
3
Running thread1....
2
Running thread1....
1
Running thread1....
0

Możesz zobaczyć, że thread1 i thread2 wykonują się naprzemiennie, ponieważ oba wymagają operacji oczekiwania (uśpienia). Gdy funkcja count_up zakończy zliczanie do 5, thread2 nie jest już aktywny. Więc otrzymujemy dane wyjściowe odpowiadające tylko thread1.

Podsumowując

W tym samouczku nauczyłeś się, jak korzystać z wbudowanego modułu wątków Pythona do implementacji wielowątkowości. Oto podsumowanie najważniejszych wniosków:

  • Konstruktor Thread może służyć do tworzenia obiektu wątku. Użycie threading.Thread(target=,args=()) tworzy wątek, który uruchamia obiekt docelowy z argumentami określonymi w args.
  • Program w języku Python działa w głównym wątku, więc tworzone obiekty wątku są dodatkowymi wątkami. Możesz wywołać funkcję active_count() zwracającą liczbę aktywnych wątków w dowolnej instancji.
  • Możesz uruchomić wątek za pomocą metody start() na obiekcie wątku i poczekać, aż zakończy się wykonywanie za pomocą metody join().

Możesz zakodować dodatkowe przykłady, dostosowując czasy oczekiwania, próbując wykonać inną operację we/wy i nie tylko. Pamiętaj, aby zaimplementować wielowątkowość w nadchodzących projektach Pythona. Udanego kodowania!🎉