Wielowątkowość a wieloprocesowość w Pythonie – threading vs multiprocessing

Kacper Sieradziński
Kacper Sieradziński
2 sierpnia 2025Edukacja3 min czytania

Python oferuje dwa główne podejścia do równoległości: wielowątkowość (threading)wieloprocesowość (multiprocessing). Wybór między nimi zależy od rodzaju zadania — I/O-bound vs CPU-bound. W tym artykule poznasz różnice między threading a multiprocessing, zrozumiesz wpływ Global Interpreter Lock (GIL), nauczysz się, kiedy użyć którego podejścia, oraz zobaczysz praktyczne przykłady obu metod.

Obraz główny Wielowątkowość a wieloprocesowość w Pythonie – threading vs multiprocessing

GIL – Global Interpreter Lock

GIL (Global Interpreter Lock) to mechanizm w CPython, który zapewnia, że tylko jeden wątek wykonuje kod Python w danym momencie. To znaczy, że wielowątkowość w Pythonie nie wykorzystuje wielu rdzeni CPU do równoległego wykonywania kodu Python.

Wpływ GIL na wydajność

  • CPU-bound zadania – threading nie przyspiesza (GIL blokuje równoległe wykonanie)
  • I/O-bound zadania – threading działa dobrze (GIL jest zwalniany podczas operacji I/O)
  • Multiprocessing – omija GIL (każdy proces ma własny interpreter)

Jeśli chcesz poznać asynchroniczność jako alternatywę, sprawdź Asynchroniczność w Pythonie z asyncio.

Threading – wielowątkowość

threading jest dobre dla zadań I/O-bound — operacji sieciowych, czytania plików, czekania na API.

Podstawowe użycie - Threading

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import threading import time import requests def fetch_url(url): """Zadanie I/O-bound""" response = requests.get(url) print(f"{url}: {response.status_code}") urls = [ "https://python.org", "https://github.com", "https://stackoverflow.com", ] # Synchroniczne wykonanie - Threading start = time.time() for url in urls: fetch_url(url) print(f"Synchroniczne: {time.time() - start:.2f}s") # Wielowątkowe wykonanie start = time.time() threads = [] for url in urls: thread = threading.Thread(target=fetch_url, args=(url,)) thread.start() threads.append(thread) for thread in threads: thread.join() print(f"Wielowątkowe: {time.time() - start:.2f}s")

ThreadPoolExecutor

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 from concurrent.futures import ThreadPoolExecutor import requests def fetch_url(url): response = requests.get(url) return response.status_code urls = ["https://python.org", "https://github.com", "https://stackoverflow.com"] with ThreadPoolExecutor(max_workers=3) as executor: results = executor.map(fetch_url, urls) for url, status in zip(urls, results): print(f"{url}: {status}")

Threading z kolejką

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import threading import queue def worker(queue): while True: item = queue.get() if item is None: break print(f"Przetwarzam: {item}") queue.task_done() q = queue.Queue() # Utwórz workerów threads = [] for i in range(3): t = threading.Thread(target=worker, args=(q,)) t.start() threads.append(t) # Dodaj zadania for i in range(10): q.put(i) # Zakończ workerów for _ in threads: q.put(None) for t in threads: t.join()

Multiprocessing – wieloprocesowość

multiprocessing jest dobre dla zadań CPU-bound — obliczeń, przetwarzania danych, algorytmów.

Podstawowe użycie - Multiprocessing

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import multiprocessing import time def cpu_intensive_task(n): """Zadanie CPU-bound""" total = 0 for i in range(n): total += i ** 2 return total # Synchroniczne wykonanie - Multiprocessing start = time.time() results = [cpu_intensive_task(10_000_000) for _ in range(4)] print(f"Synchroniczne: {time.time() - start:.2f}s") # Wieloprocesowe wykonanie start = time.time() with multiprocessing.Pool(processes=4) as pool: results = pool.map(cpu_intensive_task, [10_000_000] * 4) print(f"Wieloprocesowe: {time.time() - start:.2f}s")

ProcessPoolExecutor

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 from concurrent.futures import ProcessPoolExecutor def cpu_task(n): total = 0 for i in range(n): total += i ** 2 return total numbers = [10_000_000] * 4 with ProcessPoolExecutor(max_workers=4) as executor: results = executor.map(cpu_task, numbers) print(list(results))

Współdzielenie danych między procesami

Komunikacja między procesami wymaga specjalnych mechanizmów. Więcej o kolejkach między procesami znajdziesz w artykule Asynchroniczne kolejki z asyncio i multiprocessing.

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import multiprocessing def worker(shared_value, lock): with lock: shared_value.value += 1 if __name__ == "__main__": manager = multiprocessing.Manager() shared_value = manager.Value('i', 0) lock = manager.Lock() processes = [] for _ in range(10): p = multiprocessing.Process(target=worker, args=(shared_value, lock)) p.start() processes.append(p) for p in processes: p.join() print(f"Wartość: {shared_value.value}") # 10

Threading vs Multiprocessing – kiedy użyć?

Użyj Threading gdy:

  • ✅ Operacje I/O (sieć, pliki, baza danych)
  • ✅ Dużo czekania (API calls, user input)
  • ✅ Chcesz współdzielić stan między zadaniami
  • ✅ Niskie koszty tworzenia wątków

Użyj Multiprocessing gdy:

  • ✅ Operacje CPU-bound (obliczenia, przetwarzanie danych)
  • ✅ Chcesz wykorzystać wiele rdzeni CPU
  • ✅ Potrzebujesz prawdziwej równoległości (omija GIL)
  • ✅ Zadania są niezależne od siebie

Przykład praktyczny: mieszane podejście

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor import requests import time def fetch_data(url): """I/O-bound - użyj threading""" response = requests.get(url) return response.text[:100] def process_data(text): """CPU-bound - użyj multiprocessing""" # Symulacja przetwarzania return sum(ord(c) for c in text) urls = ["https://python.org"] * 10 # Krok 1: Pobierz dane (I/O-bound) - threading with ThreadPoolExecutor(max_workers=5) as executor: texts = list(executor.map(fetch_data, urls)) # Krok 2: Przetwórz dane (CPU-bound) - multiprocessing with ProcessPoolExecutor(max_workers=4) as executor: results = list(executor.map(process_data, texts)) print(f"Przetworzono {len(results)} elementów")

Najlepsze praktyki

1. Wybierz odpowiednie narzędzie

  • I/O-boundthreading lub asyncio
  • CPU-boundmultiprocessing

2. Używaj executor'ów

ThreadPoolExecutorProcessPoolExecutor są prostsze w użyciu niż bezpośrednie Thread/Process.

3. Unikaj zbyt wielu procesów

Zbyt wiele procesów może obciążać system. Używaj multiprocessing.cpu_count() jako wskazówki.

4. Bezpieczeństwo danych

Używaj locków, kolejek i managerów do bezpiecznego współdzielenia danych. Szczegóły o kolejkach znajdziesz w Asynchroniczne kolejki z asyncio i multiprocessing.

5. Obsługa błędów

Zawsze obsługuj wyjątki w workerach i sprawdzaj wyniki.

Podsumowanie

Wielowątkowość i wieloprocesowość to różne narzędzia do różnych zadań:

  • Threading – dla I/O-bound, wykorzystuje jeden rdzeń (GIL)
  • Multiprocessing – dla CPU-bound, wykorzystuje wiele rdzeni (omija GIL)
  • GIL – ogranicza threading dla CPU-bound zadań
  • Wybór zależy od typu zadania – I/O vs CPU

Co dalej?

Rozwijaj umiejętności współbieżności:

Pamiętaj: wybierz narzędzie odpowiednie do zadania — threading dla I/O, multiprocessing dla CPU!