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

Можно ли открывать потоки из асинхронного кода в Python?

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

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

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

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

Краткий ответ

Да, можно открывать потоки (threads) из асинхронного кода в Python, но это требует аккуратности. Потоки и асинхронный код работают в разных моделях, и их смешивание может привести к race conditions и deadlocks, если не соблюдать правила безопасности.

Почему это вызывает вопросы

Python имеет две парадигмы параллельного выполнения:

  • Асинхронный код (async/await) — использует один поток с event loop для переключения между корутинами
  • Потоки (threading) — истинный параллелизм с использованием OS-уровня потоков

Проблема в том, что асинхронный код НЕ является потокобезопасным для операций, которые блокируют event loop.

Правильный способ: run_in_executor()

Для запуска потоков из асинхронного контекста используй loop.run_in_executor():

import asyncio
from concurrent.futures import ThreadPoolExecutor

def blocking_operation(duration):
    """Блокирующая операция в отдельном потоке"""
    import time
    time.sleep(duration)
    return f"Completed in {duration}s"

async def main():
    loop = asyncio.get_event_loop()
    
    # Запускаем блокирующую операцию в thread pool
    result = await loop.run_in_executor(None, blocking_operation, 2)
    print(result)

asyncio.run(main())

Пример с ThreadPoolExecutor

import asyncio
from concurrent.futures import ThreadPoolExecutor
import time

def cpu_bound_task(n):
    """CPU-bound операция (факториал)"""
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

async def process_numbers(numbers):
    loop = asyncio.get_event_loop()
    executor = ThreadPoolExecutor(max_workers=3)
    
    tasks = [
        loop.run_in_executor(executor, cpu_bound_task, num)
        for num in numbers
    ]
    
    results = await asyncio.gather(*tasks)
    return results

async def main():
    numbers = [10, 15, 20, 25]
    results = await process_numbers(numbers)
    print(results)

asyncio.run(main())

Чего ИЗБЕГАТЬ

❌ Плохо — создание потока напрямую из async функции:

import asyncio
import threading

async def bad_approach():
    # Это работает, но может вызвать проблемы
    thread = threading.Thread(target=some_function)
    thread.start()
    # Event loop может продолжить без ожидания потока

❌ Опасно — прямое использование блокирующего кода:

async def dangerous():
    # Это заблокирует весь event loop!
    time.sleep(5)  # НИКОГДА ТАК НЕ ДЕЛАЙ
    result = requests.get("https://api.example.com")  # Блокирует!

Правила безопасности

  1. Используй run_in_executor() для блокирующего кода — это безопасно
  2. Не используй threading напрямую — сложно гарантировать безопасность
  3. Ограничивай количество потоковmax_workers должен быть разумным
  4. Используй asyncio версии библиотекaiohttp вместо requests, motor вместо pymongo
  5. Синхронизируй доступ к ресурсам — используй asyncio.Lock() вместо threading.Lock()

Практический пример: Микс async + threads

import asyncio
from concurrent.futures import ThreadPoolExecutor
import requests
import time

def fetch_from_api_sync(url):
    """Синхронный запрос (CPU-bound, I/O-bound)"""
    try:
        response = requests.get(url, timeout=5)
        return response.status_code
    except Exception as e:
        return f"Error: {e}"

async def fetch_multiple_async(urls):
    loop = asyncio.get_event_loop()
    executor = ThreadPoolExecutor(max_workers=5)
    
    # Запускаем синхронные запросы в потоках
    tasks = [
        loop.run_in_executor(executor, fetch_from_api_sync, url)
        for url in urls
    ]
    
    results = await asyncio.gather(*tasks)
    executor.shutdown(wait=True)
    return results

async def main():
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/2",
        "https://httpbin.org/delay/3",
    ]
    results = await fetch_multiple_async(urls)
    print(f"Results: {results}")

asyncio.run(main())

Итог

ДА, можно запускать потоки из async кода, но только через run_in_executor() ✅ Это безопасно и правильно ✅ Используй для блокирующего кода (sync библиотеки, CPU-bound задачи) ✅ Предпочитай асинхронные версии библиотек, когда возможно ✅ Помни о race conditions при доступе к общим ресурсам