В чем разница между конкурентностью и параллелизмом в Python

Ответ

Хотя эти термины часто путают, они описывают разные концепции.

  • Конкурентность (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).