Jak ulepszyć swój kod Pythona dzięki współbieżności i równoległości

Istotne aspekty wykonywania zadań

  • Współbieżność i równoległość to fundamenty w dziedzinie informatyki, regulujące sposób, w jaki realizowane są zadania, a każda z nich ma swoje unikalne cechy.
  • Współbieżność sprzyja efektywnemu wykorzystaniu dostępnych zasobów i poprawia interaktywność aplikacji, podczas gdy równoległość jest kluczowa dla osiągnięcia optymalnej wydajności i możliwości skalowania.
  • Python oferuje różnorodne metody implementacji współbieżności, takie jak wielowątkowość i programowanie asynchroniczne (za pomocą `asyncio`), oraz równoległość poprzez moduł `multiprocessing`.

Współbieżność i równoległość to dwa podejścia umożliwiające realizację wielu zadań, sprawiając wrażenie, że działają one równocześnie. Python udostępnia bogaty zestaw narzędzi do implementacji tych koncepcji, co może wprowadzać pewne zamieszanie.

W niniejszym artykule zgłębimy tajniki bibliotek i technik pozwalających na efektywne wdrożenie współbieżności i równoległości w Pythonie, a także omówimy zasadnicze różnice między nimi.

Zrozumienie współbieżności i równoległości

Współbieżność i równoległość to dwa fundamentalne podejścia do wykonywania zadań w informatyce. Każde z nich charakteryzuje się odrębnymi właściwościami.

  • Współbieżność to zdolność programu do zarządzania wieloma zadaniami jednocześnie, bez konieczności ich faktycznego równoczesnego wykonywania. Wykorzystuje ideę przełączania się pomiędzy zadaniami, dając wrażenie ich jednoczesnej realizacji.
  • Z kolei równoległość to wykonywanie wielu zadań faktycznie w tym samym czasie, często przy użyciu wielu rdzeni procesora. Równoległość umożliwia rzeczywistą jednoczesność działania, co przekłada się na szybsze przetwarzanie i jest szczególnie przydatne w przypadku operacji wymagających dużej mocy obliczeniowej.

Kluczowe znaczenie współbieżności i równoległości

Współbieżność i równoległość odgrywają niebagatelną rolę w dzisiejszej informatyce. Oto główne powody, dla których te techniki są tak istotne:

  • Efektywne wykorzystanie zasobów: Współbieżność pozwala na optymalne wykorzystanie zasobów systemowych, zapewniając ciągłość wykonywania zadań, a nie oczekiwanie na zasoby zewnętrzne.
  • Poprawa responsywności: Dzięki współbieżności aplikacje mogą działać płynniej i szybciej reagować na interakcje użytkownika, zwłaszcza w przypadku interfejsów graficznych i serwerów internetowych.
  • Wzrost wydajności: Równoległość jest niezbędna do osiągnięcia wysokiej wydajności, szczególnie w zadaniach wymagających intensywnych obliczeń, takich jak analiza danych, symulacje i operacje matematyczne.
  • Skalowalność systemów: Zarówno współbieżność, jak i równoległość są kluczowe dla budowy skalowalnych systemów, które mogą obsługiwać rosnące obciążenia.
  • Przyszłościowe rozwiązania: W dobie procesorów wielordzeniowych, zdolność wykorzystania równoległości staje się coraz bardziej niezbędna.

Współbieżność w Pythonie

W Pythonie współbieżność można osiągnąć za pomocą wątków i programowania asynchronicznego, wykorzystując bibliotekę `asyncio`.

Wątki w Pythonie

Wątki to mechanizm współbieżności w Pythonie, który umożliwia tworzenie i zarządzanie zadaniami w ramach jednego procesu. Wątki są szczególnie przydatne w zadaniach związanych z operacjami wejścia/wyjścia, które mogą być realizowane współbieżnie.

Moduł `threading` w Pythonie dostarcza interfejs wysokiego poziomu do tworzenia i zarządzania wątkami. Chociaż globalna blokada interpretera (GIL) uniemożliwia wątkom prawdziwą równoległość, mogą one osiągnąć współbieżność poprzez efektywne przeplatanie zadań.

Poniższy fragment kodu demonstruje użycie wątków do realizacji współbieżnych operacji. Wykorzystuje bibliotekę `requests` do wysyłania żądań HTTP, co jest typowym zadaniem blokującym wejście/wyjście, oraz moduł `time` do pomiaru czasu wykonywania.

 import requests
import time
import threading

urls = [
    'https://www.google.com',
    'https://www.wikipedia.org',
    'https://www.makeuseof.com',
]

def download_url(url):
    response = requests.get(url)
    print(f"Pobrano {url} - Status: {response.status_code}")

start_time = time.time()

for url in urls:
    download_url(url)

end_time = time.time()
print(f"Pobieranie sekwencyjne zajęło {end_time - start_time:.2f} sekund\n")

start_time = time.time()
threads = []

for url in urls:
    thread = threading.Thread(target=download_url, args=(url,))
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

end_time = time.time()
print(f"Pobieranie z użyciem wątków zajęło {end_time - start_time:.2f} sekund")

Po uruchomieniu tego programu, można zauważyć, że żądania wykonywane przy użyciu wątków są szybsze niż w przypadku wykonania sekwencyjnego. Chociaż różnica może wynosić tylko ułamki sekund, widoczna jest poprawa wydajności podczas używania wątków do zadań związanych z wejściem/wyjściem.

Programowanie asynchroniczne z Asynccio

Biblioteka `asyncio` udostępnia pętlę zdarzeń, która zarządza zadaniami asynchronicznymi, zwanymi korutynami. Korutyny to funkcje, które mogą być wstrzymywane i wznawiane, co czyni je idealnymi do zadań związanych z wejściem/wyjściem. Biblioteka ta jest szczególnie przydatna w sytuacjach, gdy zadania wymagają oczekiwania na zasoby zewnętrzne, takie jak odpowiedzi z serwerów.

Poniższy przykład pokazuje modyfikację poprzedniego kodu, aby wykorzystać `asyncio`:

import asyncio
import aiohttp
import time

urls = [
    'https://www.google.com',
    'https://www.wikipedia.org',
    'https://www.makeuseof.com',
]

async def download_url(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            content = await response.text()
            print(f"Pobrano {url} - Status: {response.status}")

async def main():
    
    tasks = [download_url(url) for url in urls]

    
    await asyncio.gather(*tasks)

start_time = time.time()

asyncio.run(main())

end_time = time.time()

print(f"Pobieranie z asyncio zajęło {end_time - start_time:.2f} sekund")

Powyższy kod ilustruje, jak można jednocześnie pobierać strony internetowe z wykorzystaniem `asyncio` i asynchronicznych operacji wejścia/wyjścia. To podejście może być bardziej wydajne niż wątki w przypadku zadań związanych z wejściem/wyjściem.

Równoległość w Pythonie

Równoległość w Pythonie można osiągnąć za pomocą modułu `multiprocessing`, który pozwala na pełne wykorzystanie potencjału procesorów wielordzeniowych.

Wieloprocesowość w Pythonie

Moduł `multiprocessing` umożliwia realizację równoległości poprzez tworzenie osobnych procesów, każdy z własnym interpreterem Pythona i przestrzenią pamięci. To omija ograniczenie globalnej blokady interpretera (GIL), czyniąc ten moduł odpowiednim do zadań obciążających procesor.

import requests
import multiprocessing
import time

urls = [
    'https://www.google.com',
    'https://www.wikipedia.org',
    'https://www.makeuseof.com',
]

def download_url(url):
    response = requests.get(url)
    print(f"Pobrano {url} - Status: {response.status_code}")

def main():
    
    num_processes = len(urls)
    pool = multiprocessing.Pool(processes=num_processes)

    start_time = time.time()
    pool.map(download_url, urls)
    end_time = time.time()

    
    pool.close()
    pool.join()

    print(f"Pobieranie wieloprocesowe zajęło {end_time-start_time:.2f} sekund")

main()

W przedstawionym przykładzie moduł `multiprocessing` tworzy wiele procesów, pozwalając na równoległe wykonanie funkcji `download_url`.

Kiedy stosować współbieżność, a kiedy równoległość?

Wybór między współbieżnością a równoległością zależy od specyfiki zadań oraz dostępnych zasobów sprzętowych.

Współbieżność jest szczególnie użyteczna w przypadku zadań związanych z wejściem/wyjściem, takich jak odczyt i zapis plików czy komunikacja sieciowa, a także w sytuacjach, gdy kluczowe są oszczędność pamięci.

Równoległość, osiągana za pomocą `multiprocessing`, jest preferowana dla zadań intensywnie obciążających procesor, gdzie istotne jest równoczesne wykonywanie operacji na wielu rdzeniach. Ponadto, stosuje się ją, gdy wymagana jest izolacja między zadaniami, tak aby awaria jednego nie wpływała na inne.

Wykorzystaj potencjał współbieżności i równoległości

Równoległość i współbieżność stanowią potężne narzędzia do optymalizacji responsywności i wydajności kodu napisanego w Pythonie. Istotne jest, aby dogłębnie rozumieć różnice między tymi koncepcjami, aby móc wybrać odpowiednie rozwiązanie.

Python oferuje bogaty zbiór narzędzi i bibliotek, które umożliwiają znaczne zwiększenie efektywności kodu poprzez zastosowanie współbieżności lub równoległości. Niezależnie od tego, czy pracujesz nad zadaniami związanymi z wejściem/wyjściem, czy obciążającymi procesor, Python ma odpowiednie narzędzia, które pomogą Ci osiągnąć zamierzony cel.