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
Python1 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 32import 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
Python1 2 3 4 5 6 7 8 9 10 11 12 13from 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ą
Python1 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 29import 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
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20import 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
Python1 2 3 4 5 6 7 8 9 10 11 12 13from 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.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21import 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
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25from 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-bound →
threadinglubasyncio - CPU-bound →
multiprocessing
2. Używaj executor'ów
ThreadPoolExecutor i ProcessPoolExecutor 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:
- Asynchroniczność w Pythonie z asyncio – alternatywa dla threading
- Asynchroniczne kolejki – komunikacja między wątkami/procesami
- Profilowanie wydajności – mierzenie efektywności
Pamiętaj: wybierz narzędzie odpowiednie do zadania — threading dla I/O, multiprocessing dla CPU!



