В чём разница между асинхронностью и параллельности?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между асинхронностью и параллельностью
Это два разных подхода к управлению множественными задачами, часто путаемые между собой. Главное отличие: параллельность - это физическая одновременность, асинхронность - это логическая одновременность.
Визуальное объяснение
Синхронный код (линейный):
Время: 1-----2-----3-----4-----5-----6
Задача: A=====A====B=====B====C=====C
Ожидание: сначала заканчивается A, потом начинается B
Параллельность (несколько потоков/процессов):
Поток 1: 1-----2-----3
Поток 2: 1-----2-----3
Процесс: A======A (в момент времени 1-3)
B======B (в момент времени 1-3)
Действительно одновременно!
Асинхронность (один поток, event loop):
Эвент лооп: 1-----2-----3-----4-----5-----6
Процесс: A..B..A..B..A..B..
(чередуются, но не одновременно)
А ждет на I/O → B может выполниться
Параллельность (Parallelism)
Это настоящее одновременное выполнение кода несколькими CPU ядрами или процессами.
import multiprocessing
import time
def cpu_bound_task(n):
"""Вычислительно интенсивная операция"""
total = 0
for i in range(n):
total += i
return total
# Последовательное выполнение (медленно)
start = time.time()
result1 = cpu_bound_task(100_000_000)
result2 = cpu_bound_task(100_000_000)
print(f"Последовательно: {time.time() - start:.2f}с")
# Вывод: Последовательно: 10.45с
# Параллельное выполнение (быстро на многоядерной системе)
start = time.time()
with multiprocessing.Pool(2) as pool:
results = pool.map(cpu_bound_task, [100_000_000, 100_000_000])
print(f"Параллельно: {time.time() - start:.2f}с")
# Вывод: Параллельно: 5.23с
Характеристики параллельности:
- Несколько CPU ядер работают одновременно
- Каждый процесс имеет свой интерпретатор Python и памяти
- Нет GIL (Global Interpreter Lock)
- Высокий overhead на создание процессов
- Подходит для CPU-bound задач
Асинхронность (Asynchrony)
Это когда одна программа (один поток) переключается между несколькими задачами, когда одна ждет (например, I/O).
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
"""Асинхронный запрос"""
async with session.get(url) as response:
return await response.text()
async def fetch_all_async():
urls = [
https://api.github.com/users/github,
https://api.github.com/users/google,
https://api.github.com/users/microsoft,
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# Асинхронное выполнение (быстро - НЕ ждет каждый запрос)
start = time.time()
results = asyncio.run(fetch_all_async())
print(f"Асинхронно: {time.time() - start:.2f}с")
# Вывод: Асинхронно: 1.5с (все 3 запроса параллельно)
Как это работает внутри:
async def main():
print("Начало 1")
await asyncio.sleep(1) # I/O операция
print("Конец 1")
print("Начало 2")
await asyncio.sleep(1) # I/O операция
print("Конец 2")
asyncio.run(main())
# Начало 1
# Начало 2 <- Переключился на вторую задачу пока первая ждет
# Конец 1 <- Первая задача готова
# Конец 2
# Общее время: ~2 сек (а не 4, как в синхронном коде)
Характеристики асинхронности:
- Один поток, один процесс
- Event loop управляет переключением между задачами
- GIL не проблема (одна задача работает за раз)
- Очень малый overhead
- Подходит для I/O-bound задач
- Требует async/await синтаксиса
Многопоточность (Threading)
Средний вариант между параллельностью и асинхронностью:
import threading
import requests
import time
from concurrent.futures import ThreadPoolExecutor
def fetch_url_sync(url):
"""Синхронный запрос"""
response = requests.get(url)
return response.status_code
def fetch_all_threading():
urls = [
https://api.github.com/users/github,
https://api.github.com/users/google,
https://api.github.com/users/microsoft,
]
with ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(fetch_url_sync, urls))
return results
# Многопоточное выполнение
start = time.time()
results = fetch_all_threading()
print(f"Многопоточно: {time.time() - start:.2f}с")
# Вывод: Многопоточно: 1.5с
Характеристики многопоточности:
- Несколько потоков в одном процессе
- GIL ограничивает истинный параллелизм CPU
- Может обмениваться памятью между потоками
- Более высокий overhead, чем asyncio
- Хороша для I/O-bound, но не для CPU-bound
- Проще в использовании, чем asyncio
Сравнительная таблица
| Характеристика | Синхронный код | Многопоточность | Асинхронность | Параллельность |
|---|---|---|---|---|
| Одновременное выполнение | Нет | Логическое | Логическое | Физическое |
| CPU ядра | 1 | 1 | 1 | Несколько |
| GIL проблема | - | Да | Нет | Нет |
| Для I/O-bound | Медленно | Быстро | Очень быстро | Быстро |
| Для CPU-bound | Нормально | Медленно | Медленно | Очень быстро |
| Complexity | Низкая | Средняя | Средняя | Высокая |
| Overhead памяти | Минимум | Средний | Минимум | Высокий |
| Пример использования | CRUD операции | Web скрейпинг | Микросервисы | Обработка больших данных |
Практические примеры
3 задачи, 3 секунды каждая (I/O):
Синхронный: ▓▓▓▓▓▓ = 9 сек
Многопоточность: ▓░░░▓░░░▓░░░ = ~3 сек
Асинхронность: ▓░░░▓░░░▓░░░ = ~3 сек
Параллельность: ▓░░░▓░░░▓░░░ = ~3 сек
3 задачи, 3 сек CPU работа:
Синхронный: ▓▓▓▓▓▓ = 9 сек
Многопоточность: ▓░▓░▓░▓░ = ~9 сек (GIL!)
Асинхронность: ▓░▓░▓░▓░ = ~9 сек
Параллельность: ▓▓▓ = ~3 сек (на 3-ядерном CPU)
Как выбрать
- I/O-bound операции (сеть, диск, БД) → asyncio (лучше всех)
- Много I/O операций → asyncio
- Чистые вычисления → multiprocessing
- Простой код, средние нагрузки → threading
- Очень простая задача → синхронный код
Важные замечания
- asyncio требует, чтобы библиотеки поддерживали async (aiohttp, asyncpg)
- threading может использовать обычные библиотеки (requests, psycopg2)
- Комбинируй подходы: asyncio + multiprocessing для гибридных нагрузок
- Не путай: concurrent != parallel