← Назад к вопросам

Можно ли совмещать потоки и асинхронность?

2.2 Middle🔥 161 комментариев
#Python Core#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Совмещение потоков и асинхронности в Python

Да, потоки и асинхронность можно совмещать, но нужно понимать особенности этого совмещения. Рассмотрим различные сценарии и лучшие практики.

Основные различия

Потоки (threading):

  • Работают с операционной системой (OS-level threads)
  • GIL (Global Interpreter Lock) блокирует одновременное выполнение Python кода
  • Хороши для I/O операций (файлы, сеть)
  • Переключение контекста управляется ОС

Асинхронность (async/await):

  • Работают в одном потоке (cooperative multitasking)
  • Нет GIL проблем для I/O операций
  • Требуют специальных асинхронных библиотек (aiohttp, asyncpg)
  • Переключение контекста управляется программой

Сценарий 1: Асинхронный код + блокирующая операция

Если в асинхронном коде нужно выполнить блокирующую операцию, используй executor:

import asyncio
import time
from concurrent.futures import ThreadPoolExecutor

async def async_function_with_blocking():
    loop = asyncio.get_event_loop()
    executor = ThreadPoolExecutor(max_workers=2)
    result = await loop.run_in_executor(executor, blocking_operation, "data")
    return result

def blocking_operation(data):
    """Блокирующая операция"""
    time.sleep(2)
    return f"Processed: {data}"

async def main():
    result = await async_function_with_blocking()
    print(result)

asyncio.run(main())

Сценарий 2: Потоки с асинхронным кодом внутри

Если создаёшь новый event loop в потоке, будь осторожен:

import asyncio
import threading

def run_async_in_thread():
    """Запуск асинхронного кода в отдельном потоке"""
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    
    try:
        result = loop.run_until_complete(async_function())
        return result
    finally:
        loop.close()

async def async_function():
    await asyncio.sleep(1)
    return "Done"

def main():
    thread = threading.Thread(target=run_async_in_thread)
    thread.start()
    thread.join()

main()

Сценарий 3: asyncio + multiprocessing

Для CPU-bound операций используй multiprocessing с асинхронностью:

import asyncio
from multiprocessing import Pool
import time

def cpu_intensive_task(n):
    """CPU-bound операция"""
    time.sleep(n)
    return n * n

async def async_with_multiprocessing():
    loop = asyncio.get_event_loop()
    with Pool(processes=4) as pool:
        tasks = [
            loop.run_in_executor(None, pool.apply_async, cpu_intensive_task, (i,))
            for i in range(5)
        ]
        results = await asyncio.gather(*tasks)
    return results

asyncio.run(async_with_multiprocessing())

Сценарий 4: asyncio с ThreadPoolExecutor для I/O

Самый распространённый случай — асинхронный код с блокирующим I/O:

import asyncio
from concurrent.futures import ThreadPoolExecutor
import requests

def make_http_request(url):
    """Блокирующий HTTP запрос"""
    response = requests.get(url)
    return response.status_code

async def fetch_multiple_urls(urls):
    loop = asyncio.get_event_loop()
    executor = ThreadPoolExecutor(max_workers=5)
    tasks = [
        loop.run_in_executor(executor, make_http_request, url)
        for url in urls
    ]
    results = await asyncio.gather(*tasks)
    return results

async def main():
    urls = ["https://example.com"] * 10
    results = await fetch_multiple_urls(urls)
    print(results)

asyncio.run(main())

Сценарий 5: Правильный способ — используй асинхронные библиотеки

Лучший подход — использовать асинхронные аналоги библиотек:

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.status

async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
    return results

async def main():
    urls = ["https://example.com"] * 10
    results = await fetch_all(urls)
    print(results)

asyncio.run(main())

Потенциальные проблемы при совмещении

GIL в многопоточности: Потоки НЕ помогают для CPU-bound кода. Используй multiprocessing вместо этого.

Deadlock при совмещении: Попытка синхронно вызвать асинхронный код внутри другого асинхронного кода приведёт к ошибке.

Рекомендации

  1. Предпочитай асинхронность для I/O операций (HTTP, БД, файлы)
  2. Используй потоки только для существующего блокирующего кода
  3. Используй multiprocessing для CPU-bound задач
  4. run_in_executor — мост между async и sync кодом
  5. Асинхронные библиотеки всегда быстрее, чем async + blocking I/O
  6. Избегай создания новых event loops в потоках, если возможно
Можно ли совмещать потоки и асинхронность? | PrepBro