CodeGym /Kursy /Python SELF PL /Moduł threading

Moduł threading

Python SELF PL
Poziom 25 , Lekcja 1
Dostępny

2.1 Moduł threading

Wielowątkowość w Pythonie to sposób wykonywania kilku wątków (threads) jednocześnie, co pozwala efektywniej wykorzystać zasoby procesora, zwłaszcza w operacjach wejścia-wyjścia lub innych zadaniach, które mogą być wykonywane równolegle.

Podstawowe pojęcia wielowątkowości w Pythonie:

Wątek — to najmniejsza jednostka wykonania, która może działać równolegle z innymi wątkami w jednym procesie. Wszystkie wątki w jednym procesie dzielą wspólną pamięć, co pozwala na wymianę danych między nimi.

Proces — to egzemplarz programu działający w systemie operacyjnym, z własnym obszarem adresowym i zasobami. W przeciwieństwie do wątków, procesy są izolowane od siebie i wymieniają dane poprzez interprocesową komunikację (IPC).

GIL — to mechanizm w interpreterze Pythona, który zapobiega równoczesnemu wykonywaniu kilku wątków Pythona. GIL zapewnia bezpieczeństwo wykonywania kodu Pythona, ale ogranicza wydajność wielowątkowych programów na procesorach wielordzeniowych.

Ważne! Pamiętaj, że z powodu Global Interpreter Lock (GIL) wielowątkowość w Pythonie może nie zapewnić znaczącego wzrostu wydajności dla zadań intensywnie obliczeniowych, ponieważ GIL zapobiega równoczesnemu wykonywaniu kilku wątków Pythona na procesorach wielordzeniowych.

Moduł threading

Moduł threading w Pythonie oferuje wysokopoziomowy interfejs do pracy z wątkami. Pozwala tworzyć i zarządzać wątkami, synchronizować je i organizować współdziałanie między nimi. Przyjrzyjmy się kluczowym komponentom i funkcjom tego modułu bardziej szczegółowo.

Główne komponenty modułu threading

Elementy do pracy z wątkami:

  • Thread — główna klasa do tworzenia i zarządzania wątkami.
  • Timer — timer do wykonania funkcji po upływie określonego czasu.
  • ThreadLocal — pozwala tworzyć dane lokalne dla wątku.

Mechanizm synchronizacji wątków:

  • Lock — prymityw synchronizacji zapobiegający równoczesnemu dostępowi do wspólnych zasobów.
  • Condition — zmienna warunkowa do bardziej złożonej synchronizacji wątków.
  • Event — prymityw do powiadamiania między wątkami.
  • Semaphore — prymityw do ograniczania liczby wątków, które mogą jednocześnie wykonywać określony obszar.
  • Barrier — synchronizuje określoną liczbę wątków, blokując je, dopóki wszystkie nie osiągną bariery.

Poniżej opowiem o 3 klasach do pracy z wątkami, natomiast mechanizm synchronizacji wątków w najbliższym czasie nie będzie ci potrzebny.

2.2 Klasa Thread

Klasa Thread — główna klasa do tworzenia i zarządzania wątkami. Ma 4 podstawowe metody:

  • start(): Rozpoczyna wykonanie wątku.
  • join(): Aktualny wątek wstrzymuje się i czeka na zakończenie uruchomionego wątku.
  • is_alive(): Zwraca True, jeśli wątek jest wykonywany.
  • run(): Metoda zawierająca kod, który będzie wykonywany w wątku. Należy ją przesłonić przy dziedziczeniu od klasy Thread.

Wszystko jest znacznie prostsze, niż się wydaje — przykład użycia klasy Thread.

Uruchomienie prostego wątku


import threading

def worker():
    print("Worker thread is running")
            
# Tworzenie nowego wątku
t = threading.Thread(target=worker) #utworzono nowy obiekt wątku
t.start() #Wystartowano wątek
t.join() # Oczekiwanie na zakończenie wątku
print("Main thread is finished")
        

Po wywołaniu metody start, funkcja worker rozpocznie swoje wykonanie. A właściwie jej wątek zostanie dodany do listy aktywnych wątków.

Używanie argumentów


import threading

def worker(number, text):
    print(f"Worker {number}: {text}")
            
# Tworzenie nowego wątku z argumentami
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
        

Aby przekazać parametry do nowego wątku, wystarczy podać je w formie krotki i przypisać do parametru args. Przy wywoływaniu funkcji, która została wskazana w target, parametry zostaną przekazane automatycznie.

Przesłanianie metody run


import threading

class MyThread(threading.Thread):
    def run(self):
        print("Custom thread is running")
            
# Tworzenie i uruchamianie wątku
t = MyThread()
t.start()
t.join()
        

Istnieją dwa sposoby wskazania funkcji, od której należy rozpocząć wykonanie nowego wątku — można ją przekazać przez parametr target przy tworzeniu obiektu Thread, lub dziedziczyć od klasy Thread i przesłonić funkcję run. Oba sposoby są legalne i używane regularnie.

2.3 Klasa Timer

Klasa Timer w module threading służy do uruchamiania funkcji po określonym czasie. Ta klasa jest przydatna do wykonywania odroczonych zadań w środowisku wielowątkowym.

Timer tworzy się i inicjalizuje za pomocą funkcji, którą należy wywołać, oraz czasu opóźnienia w sekundach.

  • Metoda start() uruchamia timer, który odlicza określony interwał czasu, a następnie wywołuje wskazaną funkcję.
  • Metoda cancel() pozwala zatrzymać timer, jeśli jeszcze się nie uruchomił. To przydatne, aby zapobiec wykonaniu funkcji, jeśli timer już nie jest potrzebny.

Przykłady użycia:

Uruchomienie funkcji z opóźnieniem

W tym przykładzie funkcja hello zostanie wywołana 5 sekund po uruchomieniu timera.


import threading

def hello():
    print("Hello, world!")
            
# Tworzenie timera, który wywoła funkcję hello po 5 sekundach
t = threading.Timer(5.0, hello)
t.start()  # Uruchomienie timera
        

Zatrzymanie timera przed wykonaniem

Tutaj timer zostanie zatrzymany, zanim funkcja hello zdąży się wykonać, więc nic nie zostanie wypisane.


import threading

def hello():
    print("Hello, world!")
            
# Tworzenie timera
t = threading.Timer(5.0, hello)
t.start()  # Uruchomienie timera
            
# Zatrzymanie timera przed wykonaniem
t.cancel()
        

Timer z argumentami

W tym przykładzie timer wywoła funkcję greet po 3 sekundach i przekaże do niej argument "Alice".


import threading

def greet(name):
    print(f"Hello, {name}!")
            
# Tworzenie timera z argumentami
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
        

Klasa Timer jest wygodna do planowania wykonywania zadań po określonym czasie. Jednocześnie timery nie gwarantują absolutnie dokładnego czasu wykonania, ponieważ zależy to od obciążenia systemu i pracy planisty wątków.

2.4 Klasa ThreadLocal

Klasa ThreadLocal jest przeznaczona do tworzenia wątków, które mają swoje własne lokalne dane. To jest przydatne w aplikacjach wielowątkowych, kiedy każdy wątek musi mieć swoją wersję danych, aby uniknąć konfliktów i problemów synchronizacji.

Każdy wątek korzystający z ThreadLocal ma swoje własne niezależne kopie danych. Dane zapisane w obiekcie ThreadLocal są unikalne dla każdego wątku i nie są współdzielone z innymi wątkami. To przydatne do przechowywania danych, które są używane tylko w kontekście jednego wątku, takich jak bieżący użytkownik w aplikacji webowej czy bieżące połączenie z bazą danych.

Przykłady użycia:

Podstawowe użycie

W tym przykładzie każdy wątek przypisuje swoją nazwę do lokalnej zmiennej value i wyświetla ją. Wartość value jest unikalna dla każdego wątku.


import threading

# Tworzenie obiektu ThreadLocal
local_data = threading.local()
            
def process_data():
    # Przypisanie wartości do lokalnej zmiennej wątku
    local_data.value = threading.current_thread().name
    # Dostęp do lokalnej zmiennej wątku
    print(f'Value in {threading.current_thread().name}: {local_data.value}')

threads = []
for i in range(5):
    t = threading.Thread(target=process_data)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Przechowywanie danych użytkownika w aplikacji webowej

W tym przykładzie każdy wątek przetwarza żądanie dla swojego użytkownika. Wartość user_data.user jest unikalna dla każdego wątku.


import threading
# Tworzenie obiektu ThreadLocal
user_data = threading.local()
def process_request(user):
    # Przypisanie wartości do lokalnej zmiennej wątku
    user_data.user = user
    handle_request()

def handle_request():
    # Dostęp do lokalnej zmiennej wątku
    print(f'Handling request for user: {user_data.user}')

threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    t = threading.Thread(target=process_request, args=(user,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

To były 3 najprzydatniejsze klasy biblioteki threading. Prawdopodobnie będziesz ich używać w swojej pracy, a reszty klas – raczej nie. Teraz wszyscy przechodzą na funkcje asynchroniczne i bibliotekę asyncio. O niej właśnie będziemy rozmawiać przez najbliższy czas.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION