Blog JSystems - uwalniamy wiedzę!

Szukaj

threading, w skrócie

Uruchomienie funkcji w osobnym wątku:

import threading

def zadanie():
    print('pracuje w watku')

t = threading.Thread(target=zadanie)
t.start()
t.join()

Wątki umożliwiają wykonywane kilku czynności równolegle. Są to częsci jednego programu (procesu) które mogą działać jednocześnie i współdzielić zasoby.

Zanim zaczniemy omawiać implementację wielowątkowości w Pythonie, musimy nieco  wyjaśnić kilka ważnych spraw.

Python jest językiem programowania ale posiada kilka implementacji. Są to m.in:

  • CPython - implementacja w C
  • PyPy - implementacja w Pythonie
  • Jython - implementacja w Javie
  • IronPython - implementacja w .NET

CPython i PyPy nie są przystosowane do wykonywania wielu czynności jednocześnie, dlatego też posiadają GIL (Global Interpreter Lock) . GIL powoduje że tylko jeden wątek ma dostęp do interpretera w danej chwili. Jest to rozwiązanie problemu z jednoczesnym dostępem do tych samych zasobów w tym samym czasie. Wszystkie wątki w związku z działaniem GILa będą szeregowane, czyli w rzeczywistości nie będą wykonywane równocześnie, a naprzemiennie. Dziać się to będzie jednak na tyle szybko, że będzie to wygladało jakby były wykonywane równolegle.

 

Pakiet threading

Przyjrzyjmy się poniższemu przykładowi:

import time
import threading
def zamul(nazwa):
    print(f'start wątku {nazwa}\n')
    time.sleep(3)
    print(f'koniec wątku {nazwa}\n')

x=threading.Thread(target=zamul,args=('boczny',))
x.start()
zamul('glówny')

Funkcja "zamul" wypisuje informacje o rozpoczęciu wątku, wstrzymuje działanie na 3 sekundy i wypisuje informację o zakończeniu wątku. Korzystając z klasy Thread znajdującej się w pakiecie threading tworzę obiekt wątku. Tworząc go, jako argumenty przekazuję funkcję do wykonania oraz argumenty które zostaną przekazane  do tej funkcji. Za pomocą "x.start()" rozpoczynam ten wątek, a chwilę później uruchamiam funkcję "zamul" bezpośrednio w głównym wątku programu.  Wynik działania na konsoli:

start wątku boczny

start wątku glówny

koniec wątku glówny

koniec wątku boczny

Jeśli uruchomisz ten kod u siebie kolejność wyświetlonych komunikatów na konsoli może być różna przy każdym uruchomieniu. Wynika to z tak zwanego zjawiska wyścigów, które są bezpośrednim skutkiem niedeterministyczności chwili wywłaszczania (przerywania i przełączania) wątków.

O tym, jak zmienia się rola programisty w czasach, gdy kod pomaga pisać AI, piszemy szerzej w osobnym artykule o pracy doświadczonego programisty w erze narzędzi AI.

 

Wątek jako demon

Zauważ że dotychczas uruchamiane wątki wykonywały się do końca, wątek główny czekał na zakończenie wszystkich wątków. Możesz też uruchomić wątek w trybie demona. Spowodujesz w ten sposób że wszystkie poboczne wątki będą kończyły pracę z zakończeniem głównego wątku programu, a główny wątek programu nie będzie czekał na zakończenie wątków bocznych. Przetestuj poniższy kod:

Jeśli dopiero zaczynasz przygodę z Pythonem, wszystkie podstawy przechodzimy krok po kroku w naszym bezpłatnym kursie Pythona od podstaw.

import time
import threading
def odliczaj(nazwa_watku, ile):
    for x in range(ile):
        print(x)
        time.sleep(1)

f=threading.Thread(target=odliczaj,args=('pierwszy',10),daemon=True)
f.start()

Program wypisuje na konsoli tylko 0, po czym kończy działanie. Gdybyś zechciał by główny wątek poczekał na zakończenie wątku demona, trzeba wywołać funkcję "join()" na obiekcie wątku:

import time
import threading
def odliczaj(nazwa_watku, ile):
    for x in range(ile):
        print(x)
        time.sleep(1)

f=threading.Thread(target=odliczaj,args=('pierwszy',10),daemon=True)
f.start()
f.join()

Wywołanie join spowoduje że główny wątek zatrzyma się na tej linii i będzie czekał z dalszym wykonaniem programu do momentu zakończenia działania wątku dziecka.

 

Odbieranie wartości z wątku

Aby odebrać wartości zwracaną z funkcji uruchomionej w ramach "bocznego" wątku musimy posłużyć się inną wysokopoziomową biblioteką Pythona:

import time
import concurrent.futures

def oddaj():
    time.sleep(3)
    return 'koza'

wykonawca=concurrent.futures.ThreadPoolExecutor()
zadanie = wykonawca.submit(oddaj)
zwrot = zadanie.result()
print(zwrot)

Chcesz opanować Pythona kompleksowo, od podstaw po zaawansowane tematy jak współbieżność? Szkolenie Python - programowanie w języku Python od podstaw ma terminy gwarantowane.

Baner szkolenia Programowanie w języku Python w JSystems, terminy gwarantowane

Szkolenie Programowanie w języku Python →

To szkolenie może być dofinansowane dla Ciebie z KFS lub BUR.

★★★★★Średnia ocena naszych szkoleń w Google: 5/5

Najczęściej zadawane pytania

Do czego służą wątki w Pythonie?
Wątki umożliwiają wykonywanie kilku czynności równolegle. Są to części jednego programu, czyli procesu, które mogą działać jednocześnie i współdzielić zasoby. Funkcję uruchamiasz w osobnym wątku, tworząc obiekt klasy Thread z pakietu threading i wywołując na nim metodę start.
Czym jest GIL i jak wpływa na wielowątkowość?
GIL, czyli Global Interpreter Lock, to blokada powodująca, że tylko jeden wątek naraz ma dostęp do interpretera. Występuje w implementacjach CPython i PyPy. Przez GIL wątki nie działają naprawdę równocześnie, tylko naprzemiennie, ale na tyle szybko, że wygląda to na równoległość.
Czym jest wątek w trybie demona?
Wątek uruchomiony jako demon kończy pracę razem z zakończeniem głównego wątku programu, a główny wątek nie czeka na jego zakończenie. Ustawiasz go, przekazując argument daemon ustawiony na prawdę przy tworzeniu obiektu wątku.
Do czego służy metoda join na obiekcie wątku?
Wywołanie metody join sprawia, że główny wątek zatrzymuje się na tej linii i czeka z dalszym wykonaniem, aż wątek dziecka zakończy działanie. Przydaje się, gdy chcesz, by program poczekał na wątek, w tym także na wątek uruchomiony jako demon.
Jak odebrać wartość zwróconą z funkcji uruchomionej w wątku?
Do tego posłuż się biblioteką concurrent.futures. Tworzysz obiekt ThreadPoolExecutor, zlecasz mu funkcję metodą submit, a wartość zwracaną odbierasz metodą result na zwróconym zadaniu. To wygodniejszy sposób niż surowe operowanie na klasie Thread.

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...