Ответ
Хотя эти термины часто путают, они описывают разные концепции.
-
Конкурентность (Concurrency) — это способность системы управлять несколькими задачами, которые выполняются в перекрывающиеся промежутки времени. Задачи могут стартовать, выполняться и завершаться в произвольном порядке, создавая иллюзию одновременности. Аналогия: один бариста принимает заказы и готовит кофе, эффективно переключаясь между задачами.
-
Параллелизм (Parallelism) — это одновременное физическое выполнение нескольких задач, как правило, на разных ядрах процессора. Аналогия: несколько бариста, каждый из которых одновременно и независимо готовит свой заказ.
В CPython ключевую роль играет GIL (Global Interpreter Lock) — механизм, который позволяет только одному потоку выполнять Python-байт-код в один момент времени.
Инструменты и их применение
1. Конкурентность: threading
и asyncio
Из-за GIL, модуль threading
не обеспечивает параллелизм для задач, нагружающих процессор (CPU-bound), но он отлично подходит для I/O-bound задач (сетевые запросы, чтение с диска). Пока один поток ждет ответа от сети, GIL освобождается, и другой поток может выполняться.
Пример с threading
(I/O-bound):
import threading
import requests
def fetch_url(url):
print(f"Fetching {url}...")
requests.get(url) # I/O-операция, GIL освобождается
print(f"Done with {url}")
urls = ["https://google.com", "https://bing.com"]
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for t in threads:
t.start() # Потоки запускаются почти одновременно
for t in threads:
t.join() # Ожидаем завершения всех потоков
asyncio
— более современный подход к конкурентности на основе событийного цикла, также идеально подходящий для I/O-bound задач.
2. Параллелизм: multiprocessing
Чтобы обойти GIL и достичь истинного параллелизма для CPU-bound задач (сложные вычисления, обработка данных), используется модуль multiprocessing
. Он создает отдельные процессы, каждый со своим интерпретатором Python и памятью, поэтому GIL не является для них общей проблемой.
Пример с multiprocessing
(CPU-bound):
from multiprocessing import Pool
def square(x):
# CPU-bound операция
return x * x
if __name__ == "__main__":
# Запускаем 4 процесса для параллельного выполнения
with Pool(4) as p:
results = p.map(square, [1, 2, 3, 4, 5, 6, 7, 8])
print(results)
# Вывод: [1, 4, 9, 16, 25, 36, 49, 64]
Итог:
- Используйте
threading
илиasyncio
для задач, которые много ждут (I/O). - Используйте
multiprocessing
для задач, которые много вычисляют (CPU).