Ответ
Переключение контекста в Python зависит от модели параллелизма: многопоточности или асинхронности.
1. Многопоточность (threading)
В CPython потоки управляются операционной системой (preemptive multitasking), но Global Interpreter Lock (GIL) позволяет только одному потоку исполнять Python-байткод в один момент времени. Переключение контекста происходит, когда:
- Поток выполняет блокирующую операцию ввода-вывода (например, чтение из сети или с диска). В этот момент он освобождает GIL, позволяя другому потоку начать выполнение.
- Истекает квант времени, выделенный потоку. Интерпретатор принудительно приостанавливает текущий поток и передает управление другому.
2. Асинхронность (asyncio)
Асинхронность использует кооперативную многозадачность (cooperative multitasking), управляемую циклом событий (event loop). Переключение контекста происходит только в явных точках — при использовании ключевого слова await.
- Когда программа встречает
await, она передает управление обратно циклу событий. - Цикл событий может запустить другую задачу или дождаться завершения асинхронной операции (например,
asyncio.sleep()или сетевого запроса).
Пример с asyncio:
import asyncio
async def task(name):
print(f"Задача {name}: начало")
# Явная точка переключения контекста.
# Управление возвращается в event loop, который может запустить другую задачу.
await asyncio.sleep(1)
print(f"Задача {name}: конец")
async def main():
# Запускаем две задачи конкурентно
await asyncio.gather(task("A"), task("B"))
asyncio.run(main())
Вывод:
Задача A: начало
Задача B: начало
# (пауза 1 секунда)
Задача A: конец
Задача B: конец Ответ 18+ 🔞
Ах ты ж ёпта, ну и тема подкинули! Переключение контекста в Python — это ж как на базаре в очереди за колбасой стоять, только вместо бабушек у тебя потоки и задачи. Слушай сюда, сейчас разжую, как божья коровка.
Так, смотри. Всё зависит от того, как ты собрался городить свою многозадачность: через потоки или через асинхронность. Это как выбрать — пилить дрова топором или бензопилой. И там, и там дерево падает, но ощущения, блядь, разные.
1. Потоки (threading) — классика жанра, но с подвохом
Вот представь: у тебя есть CPython, а в нём сидит такой злобный сторож — Global Interpreter Lock (GIL). Этот мудак не пускает больше одного потока работать с Python-кодом одновременно. Вообще. Как будто один унитаз на всю общагу. Переключение между потоками происходит в двух случаях:
- Поток полез делать что-то долгое и скучное. Читать файл с диска, ждать ответа от сервера — короче, любую блокирующую операцию ввода-вывода. В этот момент он такой: «Ой, всё!» — и выпускает из рук этот самый GIL. Другой поток тут же хватает его и начинает своё дело. Хитрая жопа, да?
- У потока кончился тайм-аут. Интерпретатор смотрит на часы и говорит: «Всё, мудила, твои полторы секунды вышли. Отдавай GIL, пусть другой поработает». И принудительно ставит поток на паузу.
Короче, переключение тут происходит за тебя, автоматически, но под присмотром этого ебучего GIL. Свободы, блядь, ноль.
2. Асинхронность (asyncio) — современная дискотека
А вот это уже поинтереснее. Тут нет принудиловки, тут кооперативная многозадачность. Это как если бы ты собрался с друзьями ремонт делать. Один красит, а второй в это время не начинает самовольно клеить обои, а ждёт, пока первый скажет: «Так, я кисточку помыл, твоя очередь, давай!».
Управляет всем этим цирком цикл событий (event loop), главный распорядитель на этой вечеринке. И переключение контекста происходит только тогда, когда ты сам этого захочешь — с помощью волшебного слова await.
Уперся код в await — всё, он сразу же, без скандала, отдаёт управление обратно в цикл событий со словами: «Я тут подожду, запускай других». Цикл событий смотрит: ага, тут Васька сетевой запрос шлёт, а тут Петька asyncio.sleep() досыпает — щас Петьку разбужу!
Смотри, как это выглядит в коде, не трогая его, как завещали:
import asyncio
async def task(name):
print(f"Задача {name}: начало")
# ВОТ ОНА, РОДНАЯ! Явная точка переключения.
# Сказал "await" — и пошёл в сторонку, пусть другие поработают.
await asyncio.sleep(1)
print(f"Задача {name}: конец")
async def main():
# Запускаем две задачи конкурентно
await asyncio.gather(task("A"), task("B"))
asyncio.run(main())
И что на выходе, спросишь? А вот что:
Задача A: начало
Задача B: начало
# (тут все задачи дружно повисли на await asyncio.sleep(1), а event loop почесал репу)
Задача A: конец
Задача B: конец
Красота, да? Всё честно, без подставы. Но помни, чувак: если какая-то задача забудет про await и начнёт в одиночку CPU долбить — все остальные просто сдохнут в ожидании. Это как если тот самый маляр возьмёт и начнёт всю квартиру один красить, а остальные так и будут с валиками стоять. Кооперация, блядь, она на доверии работает! А доверия, как известно, ебать — ноль.
Вот и вся магия. Выбирай, что тебе ближе: автоматическое переключение с надзирателем-GIL'ом или сознательная кооперация, где вся ответственность на тебе.