Какие подходы к управлению параллелизмом и конкурентностью существуют в Python

Ответ

В Python есть три основных подхода для конкурентного и параллельного выполнения кода, каждый из которых решает свой класс задач.

  1. Многопоточность (threading) Используется для конкурентного выполнения задач, ограниченных вводом-выводом (I/O-bound), таких как сетевые запросы или чтение с диска. Из-за Global Interpreter Lock (GIL) потоки в CPython не могут выполняться параллельно на нескольких ядрах CPU, но могут эффективно переключаться между собой во время ожидания I/O.

    import threading
    import time
    
    def network_request(url):
        print(f"Fetching {url}...")
        time.sleep(1) # Имитация ожидания ответа от сети
        print(f"Finished {url}")
    
    threads = [
        threading.Thread(target=network_request, args=(f"site.com/{i}",))
        for i in range(3)
    ]
    
    for t in threads: t.start()
    for t in threads: t.join()
  2. Многопроцессорность (multiprocessing) Обеспечивает истинный параллелизм, запуская задачи в отдельных процессах, каждый со своим интерпретатором Python и памятью. Это позволяет обойти GIL и полностью утилизировать все ядра CPU.

    Почему это полезно? Идеально для задач, ограниченных производительностью процессора (CPU-bound), например, математические вычисления, обработка изображений.

    from multiprocessing import Process
    
    def cpu_heavy_task(n):
        result = sum(i * i for i in range(n))
        print(f"Task finished with result: {result}")
    
    processes = [
        Process(target=cpu_heavy_task, args=(10**7,))
        for _ in range(3)
    ]
    
    for p in processes: p.start()
    for p in processes: p.join()
  3. Асинхронность (asyncio) Современный подход для конкурентного выполнения большого количества I/O-bound задач в одном потоке с помощью событийного цикла (event loop). Код пишется с использованием синтаксиса async/await.

    Почему это полезно? Очень эффективно для приложений с десятками тысяч одновременных сетевых соединений (веб-серверы, боты, парсеры).

    import asyncio
    
    async def network_request_async(url):
        print(f"Fetching {url}...")
        await asyncio.sleep(1) # Неблокирующее ожидание
        print(f"Finished {url}")
    
    async def main():
        tasks = [network_request_async(f"site.com/{i}") for i in range(3)]
        await asyncio.gather(*tasks)
    
    asyncio.run(main())

Ключевой фактор выбора:

  • I/O-bound: threading или asyncio.
  • CPU-bound: multiprocessing.