Jak ulepszyć kod Pythona dzięki współbieżności i równoległości?
Kluczowe wnioski
Współbieżność i równoległość stanowią podstawowe założenia wydajności zadań obliczeniowych, a każda z nich posiada unikalne atrybuty, które odróżniają je od siebie.
Współbieżność umożliwia efektywną alokację zasobów i zwiększoną szybkość reakcji w aplikacjach, podczas gdy równoległość odgrywa istotną rolę w osiąganiu szczytowej wydajności i możliwości skalowania.
Python oferuje wiele podejść do zarządzania współbieżnymi operacjami, w tym wykorzystanie wątków poprzez wbudowaną bibliotekę wątków, a także wsparcie dla programowania asynchronicznego przy użyciu frameworka asyncio. Dodatkowo, moduł multiprocessingu pozwala programistom wykorzystać moc przetwarzania równoległego w swoich aplikacjach.
Współbieżność odnosi się do zdolności systemu do wykonywania wielu procesów lub wątków w tym samym czasie, podczas gdy równoległość to zdolność do dzielenia zadania na mniejsze podzadania i wykonywania ich jednocześnie przez różne części systemu. W Pythonie dostępne są różne podejścia do zarządzania współbieżnością i równoległością, takie jak wieloprocesowość, wątkowanie, programowanie asynchroniczne z async/await oraz korzystanie z bibliotek takich jak Celery lub Dask do obliczeń rozproszonych. Opcje te mogą jednak prowadzić do dezorientacji przy podejmowaniu decyzji o najbardziej odpowiednim podejściu do konkretnej sytuacji.
Zapoznaj się z szeregiem zasobów i frameworków, które mogą skutecznie ułatwić implementację technik programowania współbieżnego w Pythonie, a także z ich różnicami między sobą.
Zrozumienie współbieżności i równoległości
Współbieżność i równoległość to dwie ważne koncepcje opisujące sposób wykonywania zadań w systemach komputerowych, z których każda ma swoje unikalne atrybuty.
⭐ Współbieżność to zdolność programu do zarządzania wieloma zadaniami w tym samym czasie, bez konieczności wykonywania ich dokładnie w tym samym czasie. Obraca się wokół idei przeplatania zadań, przełączania się między nimi w sposób, który wydaje się równoczesny.
⭐ Z drugiej strony, równoległość polega na równoległym wykonywaniu wielu zadań. Zazwyczaj wykorzystuje ona wiele rdzeni lub procesorów CPU. Równoległość zapewnia prawdziwie równoczesne wykonywanie, umożliwiając szybsze wykonywanie zadań i dobrze nadaje się do intensywnych obliczeniowo operacji.
Znaczenie współbieżności i równoległości
Znaczenie przetwarzania współbieżnego i równoległego w informatyce jest niepodważalne, ponieważ umożliwia jednoczesne wykonywanie wielu zadań, zwiększając w ten sposób wydajność i skracając ogólny czas wykonania.Podejście to staje się coraz ważniejsze ze względu na rosnące zapotrzebowanie na szybsze i bardziej wydajne możliwości przetwarzania w szerokim zakresie zastosowań, od symulacji naukowych po automatyzację procesów biznesowych. Wykorzystując moc procesorów wielordzeniowych i systemów rozproszonych, przetwarzanie współbieżne i równoległe pozwala na lepsze wykorzystanie zasobów i zwiększoną skalowalność, co ostatecznie prowadzi do lepszej wydajności i zwiększonej produktywności.
Zoptymalizowana alokacja zasobów jest osiągalna dzięki współbieżności, ponieważ umożliwia efektywne wykorzystanie zasobów systemowych, gwarantując, że procesy będą nadal rozwijać się produktywnie, zamiast biernie czekać na zasoby zewnętrzne.
Możliwość zwiększenia responsywności aplikacji, szczególnie w odniesieniu do interfejsu użytkownika i interakcji z serwerem WWW, jest istotną zaletą związaną ze współbieżnością.
Zwiększoną wydajność można osiągnąć dzięki równoległości, zwłaszcza w zadaniach obliczeniowych, które są w dużym stopniu zależne od jednostek centralnych (CPU), takich jak skomplikowane obliczenia, manipulacja danymi i symulacje modelowania.
Skalowalność jest krytycznym aspektem projektowania systemu, wymagającym zarówno współbieżnego wykonywania, jak i przetwarzania równoległego w celu osiągnięcia optymalnej wydajności na dużą skalę. Zdolność do radzenia sobie z rosnącymi obciążeniami przy jednoczesnym zachowaniu wydajności jest najważniejsza w rozwoju nowoczesnego oprogramowania.
W świetle pojawiających się trendów w technologii sprzętowej, które nadają priorytet możliwościom przetwarzania wielordzeniowego, konieczne stało się, aby systemy oprogramowania skutecznie wykorzystywały równoległość w celu zapewnienia ich długoterminowej żywotności i trwałości.
Współbieżność w Pythonie
Współbieżne wykonywanie można osiągnąć w Pythonie poprzez wykorzystanie wątków lub technik asynchronicznych, które są ułatwione dzięki bibliotece asyncio.
Wątkowanie w Pythonie
Wątkowanie jest nieodłączną cechą programowania w Pythonie, która umożliwia tworzenie i zarządzanie wieloma współbieżnymi zadaniami w ramach zunifikowanego procesu. Mechanizm ten okazuje się szczególnie korzystny w przypadku zadań z dużymi operacjami wejścia/wyjścia lub takich, które mogą korzystać z równoległego wykonywania.
Moduł wątków Pythona zapewnia interfejs wysokiego poziomu do tworzenia i zarządzania wątkami. Podczas gdy GIL (Global Interpreter Lock) ogranicza wątki pod względem prawdziwej równoległości, mogą one nadal osiągać współbieżność poprzez efektywne przeplatanie zadań.
Podany kod demonstruje przykład programowania współbieżnego poprzez wykorzystanie wątków w Pythonie.W szczególności wykorzystuje bibliotekę Python Request do inicjowania żądania HTTP, które jest typową operacją obejmującą operacje wejścia-wyjścia (I/O) i może powodować blokowanie zadań. Dodatkowo, kod wykorzystuje moduł czasu do określenia czasu wykonywania programu.
import requests
import time
import threading
urls = [
'https://www.google.com',
'https://www.wikipedia.org',
'https://www.makeuseof.com',
]
# function to request a URL
def download_url(url):
response = requests.get(url)
print(f"Downloaded {url} - Status Code: {response.status_code}")
# Execute without threads and measure execution time
start_time = time.time()
for url in urls:
download_url(url)
end_time = time.time()
print(f"Sequential download took {end_time - start_time:.2f} seconds\n")
# Execute with threads, resetting the time to measure new execution time
start_time = time.time()
threads = []
for url in urls:
thread = threading.Thread(target=download_url, args=(url,))
thread.start()
threads.append(thread)
# Wait for all threads to complete
for thread in threads:
thread.join()
end_time = time.time()
print(f"Threaded download took {end_time - start_time:.2f} seconds")
Rzeczywiście, wykonanie tego programu ujawnia zauważalny wzrost wydajności przy wykorzystaniu współbieżnych wątków do wykonywania operacji intensywnie wykorzystujących wejścia/wyjścia, pomimo marginalnej różnicy czasu między sekwencyjnym i równoległym wykonaniem.
Programowanie asynchroniczne z asyncio
asyncio zapewnia pętlę zdarzeń, która zarządza zadaniami asynchronicznymi zwanymi coroutine. Coroutine to funkcje, które można wstrzymywać i wznawiać, dzięki czemu idealnie nadają się do zadań związanych z wejściami/wyjściami. Biblioteka ta jest szczególnie przydatna w scenariuszach, w których zadania wymagają oczekiwania na zasoby zewnętrzne, takie jak żądania sieciowe.
Aby dostosować poprzedni przykład synchronicznego wysyłania żądań do programowania asynchronicznego w Pythonie, należy wprowadzić kilka zmian. Po pierwsze, zamiast używać requests.get()
i time.sleep()
, które są operacjami blokującymi, które wstrzymują wykonanie odpowiednio do zakończenia lub upływu czasu, należy wykorzystać alternatywy bez blokowania, takie jak asyncio
i aiohttp
. Wiąże się to z opakowaniem istniejącego kodu w funkcję asynchroniczną przy użyciu async def
i zastąpieniem tradycyjnych operacji I/O ich asynchronicznymi odpowiednikami. Na przykład, async z aiohttp.ClientSession().post(url)
może być użyty do asynchronicznego wysłania żądania POST bez oczekiwania na odpowiedź. Ponadto obsługa błędów i rejestrowanie mogą również wymagać dostosowania w celu dostosowania do nowej struktury asynchronicznej.
import asyncio
import aiohttp
import time
urls = [
'https://www.google.com',
'https://www.wikipedia.org',
'https://www.makeuseof.com',
]
# asynchronous function to request URL
async def download_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
content = await response.text()
print(f"Downloaded {url} - Status Code: {response.status}")
# Main asynchronous function
async def main():
# Create a list of tasks to download each URL concurrently
tasks = [download_url(url) for url in urls]
# Gather and execute the tasks concurrently
await asyncio.gather(*tasks)
start_time = time.time()
# Run the main asynchronous function
asyncio.run(main())
end_time = time.time()
print(f"Asyncio download took {end_time - start_time:.2f} seconds")
Wykorzystując dostarczony kod, można wydajnie wykonywać wiele jednoczesnych pobrań stron internetowych, wykorzystując możliwości asyncio
i wykorzystując moc asynchronicznych operacji we / wy. W przeciwieństwie do tradycyjnych technik wątkowania, które lepiej nadają się do zadań wymagających dużej mocy obliczeniowej procesora, podejście to jest szczególnie skuteczne w optymalizacji procesów związanych z operacjami wejścia/wyjścia.
Równoległość w Pythonie
Równoległość można zaimplementować przy użyciu modułu wieloprocesowego Pythona , który pozwala w pełni wykorzystać możliwości procesorów wielordzeniowych.
Wieloprzetwarzanie w Pythonie
Moduł wieloprzetwarzania Pythona oferuje podejście do wykorzystania równoległości poprzez tworzenie indywidualnych procesów, z których każdy wyposażony jest w odrębny interpreter Pythona i domenę pamięci. W ten sposób metoda ta omija globalną blokadę interpretera (GIL), która jest powszechnie spotykana w zadaniach związanych z procesorem.
import requests
import multiprocessing
import time
urls = [
'https://www.google.com',
'https://www.wikipedia.org',
'https://www.makeuseof.com',
]
# function to request a URL
def download_url(url):
response = requests.get(url)
print(f"Downloaded {url} - Status Code: {response.status_code}")
def main():
# Create a multiprocessing pool with a specified number of processes
num_processes = len(urls)
pool = multiprocessing.Pool(processes=num_processes)
start_time = time.time()
pool.map(download_url, urls)
end_time = time.time()
# Close the pool and wait for all processes to finish
pool.close()
pool.join()
print(f"Multiprocessing download took {end_time-start_time:.2f} seconds")
main()
Multiprocessing umożliwia współbieżne wykonywanie funkcji download_url
przez wiele procesów, ułatwiając w ten sposób równoległe przetwarzanie danego adresu URL.
Kiedy używać współbieżności lub równoległości
Decyzja o wyborze przetwarzania współbieżnego lub równoległego zależy od charakterystyki wykonywanych operacji i pojemności zasobów sprzętowych systemu bazowego.
Podczas obsługi operacji koncentrujących się na wejściu/wyjściu (I/O), takich jak operacje odczytu/zapisu plików lub żądania sieciowe, zaleca się wykorzystanie współbieżności ze względu na jej zdolność do obsługi wielu zadań jednocześnie. Dodatkowo, może być ona stosowana w sytuacjach, w których ograniczenia pamięci stanowią wyzwanie.
Gdy wykorzystanie wieloprocesowości jest odpowiednie dla operacji wymagających dużej mocy obliczeniowej procesora, które mogą być usprawnione przez współbieżne wykonywanie i gdy zapewnienie solidnej separacji procesów jest priorytetem, ponieważ awaria każdego procesu nie powinna nadmiernie wpływać na inne.
Wykorzystanie współbieżności i równoległości
Równoległość i współbieżne wykonywanie są realnymi metodami zwiększania wydajności i przepustowości programów Python. Kluczowe jest jednak zrozumienie różnic między tymi technikami, aby podjąć świadomą decyzję dotyczącą tego, które podejście najlepiej pasuje do konkretnej sytuacji.
Python zapewnia kompleksowy zestaw narzędzi i modułów, które umożliwiają programistom zwiększenie wydajności ich kodu poprzez wykorzystanie technik przetwarzania współbieżnego lub równoległego, niezależnie od tego, czy dane zadania mają przede wszystkim charakter obliczeniowy, czy też wymagają dużej ilości danych wejściowych/wyjściowych.