Ответ
В Python есть три основных подхода для конкурентного и параллельного выполнения кода, каждый из которых решает свой класс задач.
-
Многопоточность (
threading) Используется для конкурентного выполнения задач, ограниченных вводом-выводом (I/O-bound), таких как сетевые запросы или чтение с диска. Из-за Global Interpreter Lock (GIL) потоки в CPython не могут выполняться параллельно на нескольких ядрах CPU, но могут эффективно переключаться между собой во время ожидания I/O.import threading import time def network_request(url): print(f"Fetching {url}...") time.sleep(1) # Имитация ожидания ответа от сети print(f"Finished {url}") threads = [ threading.Thread(target=network_request, args=(f"site.com/{i}",)) for i in range(3) ] for t in threads: t.start() for t in threads: t.join() -
Многопроцессорность (
multiprocessing) Обеспечивает истинный параллелизм, запуская задачи в отдельных процессах, каждый со своим интерпретатором Python и памятью. Это позволяет обойти GIL и полностью утилизировать все ядра CPU.Почему это полезно? Идеально для задач, ограниченных производительностью процессора (CPU-bound), например, математические вычисления, обработка изображений.
from multiprocessing import Process def cpu_heavy_task(n): result = sum(i * i for i in range(n)) print(f"Task finished with result: {result}") processes = [ Process(target=cpu_heavy_task, args=(10**7,)) for _ in range(3) ] for p in processes: p.start() for p in processes: p.join() -
Асинхронность (
asyncio) Современный подход для конкурентного выполнения большого количества I/O-bound задач в одном потоке с помощью событийного цикла (event loop). Код пишется с использованием синтаксисаasync/await.Почему это полезно? Очень эффективно для приложений с десятками тысяч одновременных сетевых соединений (веб-серверы, боты, парсеры).
import asyncio async def network_request_async(url): print(f"Fetching {url}...") await asyncio.sleep(1) # Неблокирующее ожидание print(f"Finished {url}") async def main(): tasks = [network_request_async(f"site.com/{i}") for i in range(3)] await asyncio.gather(*tasks) asyncio.run(main())
Ключевой фактор выбора:
- I/O-bound:
threadingилиasyncio. - CPU-bound:
multiprocessing.
Ответ 18+ 🔞
Давай разберём эту тему, а то я вижу, у людей тут иногда в голове каша, будто они муку с цементом перепутали.
Вот смотри, в Питоне есть три главных способа заставить твой код не сидеть на жопе ровно, а делать несколько дел как бы одновременно. И каждый из них — для своей, блядь, конкретной ситуации. Если их перепутать, получится как в том анекдоте: «Я его лечил-лечил, а он всё равно сдох».
1. Многопоточность (threading) Это когда ты хочешь, чтобы твоя программа не тупила, пока ждёт ответа от какого-нибудь медленного сервера или читает с диска. Задачи тут конкурентные, то есть они по очереди быстро-быстро переключаются, пока одна ждёт. Но есть одна огромная, жирная, блядь, проблема — GIL (Global Interpreter Lock). Эта штука в CPython не даёт потокам реально работать параллельно на нескольких ядрах процессора. Они как мартышки на одной верёвке: дергаются все, но бегут по одной.
import threading
import time
def network_request(url):
print(f"Fetching {url}...")
time.sleep(1) # Прикидываемся, что ждём ответ из сети
print(f"Finished {url}")
threads = [
threading.Thread(target=network_request, args=(f"site.com/{i}",))
for i in range(3)
]
for t in threads: t.start()
for t in threads: t.join()
Короче, если твоя программа много ждёт (сеть, диск) — это твой вариант. Хуй с ним, с GIL, пока одна нить спит, другая может работать.
2. Многопроцессорность (multiprocessing) А вот это уже серьёзно, ёпта! Это когда тебе нужно настоящее параллельное выполнение, чтобы загрузить все ядра твоего процессора по полной программе. Здесь каждая задача запускается в отдельном процессе, со своим собственным интерпретатором и памятью. GIL? Да похуй на него! Он в каждом процессе свой, так что они не мешают друг другу.
Зачем это надо? Представь, что тебе нужно перемолоть кучу чисел, обработать тонну изображений или что-то такое, где процессор должен пахать, а не ждать. Вот для таких CPU-bound задач — это твой выбор, золотой.
from multiprocessing import Process
def cpu_heavy_task(n):
result = sum(i * i for i in range(n))
print(f"Task finished with result: {result}")
processes = [
Process(target=cpu_heavy_task, args=(10**7,))
for _ in range(3)
]
for p in processes: p.start()
for p in processes: p.join()
Запустишь — и твой комп загудит как пылесос, потому что все ядра в деле. Красота!
3. Асинхронность (asyncio)
А это, блядь, модная молодёжная тема. Современный подход, чтобы в одном потоке управлять кучей I/O-задач. Всё крутится вокруг событийного цикла (event loop) и магии async/await.
Когда это охуенно? Когда у тебя не три запроса к сайту, а тридцать тысяч. Веб-серверы, чат-боты, парсеры — всё, что работает с сетью и должно обслуживать дохуище соединений одновременно, не создавая дохуища потоков или процессов.
import asyncio
async def network_request_async(url):
print(f"Fetching {url}...")
await asyncio.sleep(1) # Тут мы не блокируем весь мир, а просто говорим "разбудите через секунду"
print(f"Finished {url}")
async def main():
tasks = [network_request_async(f"site.com/{i}") for i in range(3)]
await asyncio.gather(*tasks)
asyncio.run(main())
Выглядит непривычно, но зато эффективно, как хитрая жопа.
Итог, чтобы не ебать себе мозг:
- Задача много ждёт (I/O-bound): Бери
threadingили учиasyncio. - Задача много считает (CPU-bound): Только
multiprocessing, иначе GIL превратит твои потоки в одного унылого работягу.
Выбирай с умом, а то потом будешь сидеть и думать: «Ну почему же оно такое медленное, ебать мои старые костыли!»