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

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

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

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

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

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

Разница между асинхронностью с использованием потоков и asyncio

Это два принципиально разных подхода к параллельной обработке в Python. Оба решают проблему блокирования, но по-разному.

Потоки (threading)

Потоки используют истинный параллелизм на многоядерных системах, но в Python работают через Global Interpreter Lock (GIL).

import threading
import time

def worker(name):
    for i in range(3):
        print(f"{name}: {i}")
        time.sleep(1)  # Блокирует поток

# Создаём и запускаем потоки
threads = []
for i in range(2):
    t = threading.Thread(target=worker, args=(f"Thread-{i}",))
    t.start()
    threads.append(t)

# Ждём завершения
for t in threads:
    t.join()

Как работает:

  • ОС управляет переключением между потоками
  • Каждый поток может работать на отдельном ядре (если нет GIL)
  • Потоки имеют собственный стек вызовов

asyncio

asyncio использует кооперативную многозадачность — одна нить выполнения переключается между корутинами.

import asyncio

async def worker(name):
    for i in range(3):
        print(f"{name}: {i}")
        await asyncio.sleep(1)  # Не блокирует, позволяет другим работать

async def main():
    # Создаём задачи
    tasks = [worker(f"Task-{i}") for i in range(2)]
    # Запускаем параллельно
    await asyncio.gather(*tasks)

asyncio.run(main())

Как работает:

  • Одна нить выполнения переключается между корутинами
  • Контекст переключается только в точках await
  • Предсказуемо — нет race conditions

Ключевые отличия

АспектПотокиasyncio
УровеньОС (многозадачность)Приложение (однозадачность)
НитиНесколько нитейОдна нить
ПереключениеКогда хочет ОСВ точках await
GILБлокирует (CPU-bound)Не проблема
Быстродействие стартаМедленно (100+ мс)Быстро (1+ мс)
MemoryМного (8+ МБ на поток)Мало (50+ КБ на корутину)
Race conditionsЧастоРедко
СинхронизацияLocks, RLocks, EventsНет нужды (однопоточность)

Производительность I/O

Потоки хороши для I/O:

import threading
import requests

def fetch(url):
    response = requests.get(url)  # Блокирующий вызов
    print(f"Fetched: {response.status_code}")

threads = [threading.Thread(target=fetch, args=(url,)) for url in urls]
for t in threads:
    t.start()

asyncio ещё лучше для I/O:

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:  # Не блокирует
        print(f"Fetched: {response.status}")

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

asyncio.run(main())

CPU-bound операции

Потоки: будут по-прежнему медленны из-за GIL.

import threading

def cpu_work(n):
    return sum(i*i for i in range(n))  # GIL блокирует

# Потоки НЕ ускорят это
threads = [threading.Thread(target=cpu_work, args=(10000000,)) for _ in range(4)]

asyncio: не поможет, нужна multiprocessing.

from multiprocessing import Pool

def cpu_work(n):
    return sum(i*i for i in range(n))

with Pool(4) as p:
    results = p.map(cpu_work, [10000000]*4)  # Истинный параллелизм

Проблемы потоков

1. Race conditions:

counter = 0

def increment():
    global counter
    for _ in range(1000000):
        counter += 1  # Race condition!

threads = [threading.Thread(target=increment) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(counter)  # < 4000000 вместо ожидаемого

2. Deadlocks:

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1():
    lock1.acquire()
    time.sleep(0.1)
    lock2.acquire()  # Deadlock

def thread2():
    lock2.acquire()
    lock1.acquire()  # Deadlock

Проблемы asyncio

1. Blocking call блокирует весь event loop:

import asyncio
import time

async def bad_example():
    print("Start")
    time.sleep(2)  # Блокирует весь loop!
    print("End")

# Вместо этого:
async def good_example():
    print("Start")
    await asyncio.sleep(2)  # Не блокирует
    print("End")

2. Требуется async/await всей цепочке:

# Если используешь asyncio, все зависимости должны быть async
async def fetch_data():
    return await some_async_call()

# Это не сработает без await
result = fetch_data()  # Вернёт coroutine, не данные

Когда использовать потоки

  1. I/O с блокирующими библиотеками: requests, sqlite3, blockchain RPCs
  2. Простые параллельные задачи: веб-скрейпинг, обработка файлов
  3. Интеграция с C-библиотеками: OpenCV, scikit-learn
from concurrent.futures import ThreadPoolExecutor

def download_file(url):
    response = requests.get(url)
    return response.content

with ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(download_file, urls))

Когда использовать asyncio

  1. I/O с async библиотеками: aiohttp, asyncpg, motor
  2. Высокая конкурентность: тысячи одновременных подключений
  3. WebSocket, gRPC: реал-тайм приложения
import asyncio
import aiohttp

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

asyncio.run(main())

Гибридный подход

Combine потоки и asyncio для комплексных случаев:

import asyncio
from concurrent.futures import ThreadPoolExecutor
import requests

async def fetch_with_thread(url):
    loop = asyncio.get_event_loop()
    # Запускаем blocking запрос в отдельном потоке
    return await loop.run_in_executor(
        ThreadPoolExecutor(),
        requests.get,
        url
    )

async def main():
    tasks = [fetch_with_thread(url) for url in urls]
    results = await asyncio.gather(*tasks)

asyncio.run(main())

Производительность — реальные числа

Запуск 1000 I/O операций (1мс каждая):

  • requests + threads: 10-15 секунд (из-за overhead потоков)
  • aiohttp + asyncio: 1 секунда
  • блокирующий код: 1000 секунд

Заключение

  • Потоки: подходят для старого кода с blocking I/O, простые случаи
  • asyncio: современный стандарт Python, для высоконагруженных I/O сервисов

Для новых проектов выбирай asyncio. Для интеграции с legacy кодом используй потоки или гибридный подход.