← Назад к вопросам
Какой вид многопоточности будешь использовать для рассылки писем?
2.3 Middle🔥 131 комментариев
#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Выбор многопоточности для рассылки писем
Для рассылки писем я бы использовал асинхронное программирование (asyncio), а не потоки. Вот почему и как это работает:
Почему именно asyncio, а не threading?
Threading (потоки):
- GIL (Global Interpreter Lock) блокирует параллельное выполнение Python кода
- Подходит только для I/O-bound операций
- Сложнее отлаживать и возникают race conditions
- Дороже по памяти (каждый поток занимает ~8MB)
Asyncio (асинхронность):
- Не подвержен GIL, так как работает в одном потоке
- Идеален для I/O-bound задач (отправка писем — это I/O)
- Простой контроль потока выполнения через await
- Экономнее памяти (можно запустить тысячи корутин)
- Естественный способ работы с сетью и почтой
Реализация с asyncio
import asyncio
import aiosmtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class EmailSender:
def __init__(self, smtp_host: str, smtp_port: int):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
async def send_email(self, to: str, subject: str, body: str) -> bool:
"""Отправить одно письмо асинхронно"""
try:
async with aiosmtplib.SMTP(hostname=self.smtp_host, port=self.smtp_port) as smtp:
message = MIMEMultipart()
message["From"] = "noreply@example.com"
message["To"] = to
message["Subject"] = subject
message.attach(MIMEText(body, "html"))
await smtp.send_message(message)
return True
except Exception as e:
print(f"Ошибка отправки письма {to}: {e}")
return False
async def send_bulk(
self,
recipients: list[dict], # [{"email": "...", "subject": "...", "body": "..."}, ...]
max_concurrent: int = 10
) -> dict:
"""Отправить письма с ограничением на одновременные запросы"""
semaphore = asyncio.Semaphore(max_concurrent)
async def send_with_limit(recipient):
async with semaphore:
return await self.send_email(
to=recipient["email"],
subject=recipient["subject"],
body=recipient["body"]
)
results = await asyncio.gather(
*[send_with_limit(r) for r in recipients],
return_exceptions=True
)
return {
"total": len(recipients),
"sent": sum(1 for r in results if r is True),
"failed": sum(1 for r in results if r is not True)
}
# Использование
async def main():
sender = EmailSender(smtp_host="smtp.gmail.com", smtp_port=587)
recipients = [
{"email": "user1@example.com", "subject": "Привет", "body": "Содержание письма"},
{"email": "user2@example.com", "subject": "Привет", "body": "Содержание письма"},
# ... тысячи писем
]
result = await sender.send_bulk(recipients, max_concurrent=10)
print(f"Результат: {result}")
asyncio.run(main())
Альтернатива: Celery (для очередей)
Для enterprise-решений использую Celery с Redis/RabbitMQ:
from celery import Celery, group
from celery.result import GroupResult
app = Celery("mail_service", broker="redis://localhost:6379")
@app.task(bind=True, max_retries=3)
def send_email_task(self, to: str, subject: str, body: str):
"""Задача для отправки письма с повторами"""
try:
# SMTP логика здесь
return {"status": "sent", "email": to}
except Exception as exc:
# Повтор через 60 сек
raise self.retry(exc=exc, countdown=60)
def send_bulk_emails(recipients: list[dict]):
"""Поставить задачи в очередь"""
job = group(
send_email_task.s(
to=r["email"],
subject=r["subject"],
body=r["body"]
)
for r in recipients
)
return job.apply_async()
Ключевые моменты при выборе
Используй asyncio если:
- Письма отправляются синхронно в одном процессе
- Нужна простота развертывания
- < 10,000 писем в день
- Письма некритичны по надежности
Используй Celery если:
- Критична надежность доставки
- Нужны повторы при ошибках
- Письма отправляются из разных мест приложения
- Нужна обработка в фоне
-
10,000 писем в день
Бонус: Использование thread pool для блокирующих операций
Если необходимо вызвать синхронную библиотеку SMTP:
import asyncio
from concurrent.futures import ThreadPoolExecutor
import smtplib
loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=5)
async def send_email_threaded(to: str, subject: str):
def _send():
# Синхронный SMTP код
with smtplib.SMTP("smtp.gmail.com") as smtp:
smtp.send_message(...)
return await loop.run_in_executor(executor, _send)
Вывод: Для рассылки писем asyncio + aiosmtplib — идеальный выбор благодаря эффективности, простоте и отсутствию GIL проблем.