diff --git a/python/schemes/multiprocessing.puml b/python/schemes/multiprocessing.puml new file mode 100644 index 0000000..da88e39 --- /dev/null +++ b/python/schemes/multiprocessing.puml @@ -0,0 +1,140 @@ +@startuml +title Работа multiprocessing.Queue с большими объектами\n(2 worker процесса) + +participant "Главный процесс" as Main +participant "Queue\n(pipe buffer ~65KB)" as Queue +participant "Worker 1" as W1 +participant "Worker 2" as W2 + +== Запуск процессов == + +Main -> W1 ** : start() +Main -> W2 ** : start() + +note over Main + Оба worker'а работают + параллельно в фоне +end note + +== Worker 1: Отправка больших данных == + +W1 -> W1 : Обработка данных +activate W1 #lightblue +W1 -> W1 : events = [...] (большой массив) +W1 -> Queue : put({"events": events})\n[Начало сериализации] +activate Queue #orange + +note right of W1 + pickle сериализует объект: + ОБЪЕКТ_1 → [500 KB байт] +end note + +W1 -> Queue : Запись chunk 1 (64 KB) +W1 -> Queue : Запись chunk 2 (64 KB) + +note over Queue + Pipe buffer заполнен! + Больше нельзя записать +end note + +W1 -[#red]> W1 : ⏸️ БЛОКИРОВКА на put() +note right of W1 #pink + Worker 1 ЖДЕТ, + пока освободится место +end note + +== Worker 2: Тоже пытается отправить == + +W2 -> W2 : Обработка данных +activate W2 #lightgreen +W2 -> W2 : events = [...] (большой массив) +W2 -> Queue : put({"events": events}) +W2 -[#red]> W2 : ⏸️ БЛОКИРОВКА\n(очередь занята Worker 1) + +== Главный процесс: Чтение (итерация 1) == + +Main -> Main : for i in range(2):\n result = queue.get() +activate Main #yellow + +Main -> Queue : get() [вызов] +note right of Main #lightgreen + Первая итерация цикла + i = 0 +end note + +Queue -> Queue : Читает ВСЕ байты ОБЪЕКТА_1 +note over Queue + ✓ Читает chunk 1 (64 KB) + ✓ Читает chunk 2 (64 KB) + ✓ Читает chunk 3... + ... + ✓ Читает последний chunk + + ВАЖНО: get() не вернется, + пока не прочитает ВЕСЬ объект! +end note + +Queue -> Main : return ПОЛНЫЙ ОБЪЕКТ_1 +note right of Main + Получен ЦЕЛЫЙ объект + от Worker 1 +end note + +note over Queue #lightgreen + Pipe buffer освобожден! + Теперь Worker 1 может + продолжить запись +end note + +W1 -[#green]> W1 : ✓ Разблокировка! +W1 -> W1 : put() завершен +deactivate W1 + +note over Queue + Теперь очередь пустая, + Worker 2 может записать +end note + +W2 -[#green]> W2 : ✓ Разблокировка! +W2 -> Queue : Запись ОБЪЕКТА_2 +activate Queue #orange +W2 -> W2 : put() завершен +deactivate W2 + +== Главный процесс: Чтение (итерация 2) == + +Main -> Queue : get() [второй вызов] +note right of Main #lightgreen + Вторая итерация цикла + i = 1 +end note + +Queue -> Queue : Читает ВСЕ байты ОБЪЕКТА_2 +Queue -> Main : return ПОЛНЫЙ ОБЪЕКТ_2 +deactivate Queue + +note right of Main + Получен ЦЕЛЫЙ объект + от Worker 2 + + ДАННЫЕ НЕ СМЕШАЛИСЬ! + Каждый get() вернул + полный объект +end note + +== Завершение == + +Main -> W1 : join() +W1 --> Main : ✓ завершен +Main -> W2 : join() +W2 --> Main : ✓ завершен + +deactivate Main + +note over Main, W2 #lightgreen + ✓ Все данные получены полностью + ✓ Ничего не потеряно + ✓ Объекты не смешались +end note + +@enduml diff --git a/python/to_thread/timeline.puml b/python/to_thread/timeline.puml index e69de29..4b5366c 100644 --- a/python/to_thread/timeline.puml +++ b/python/to_thread/timeline.puml @@ -0,0 +1,122 @@ +@startuml gil_switching_mechanism +title GIL Switching - что происходит на микроуровне + +participant "Event Loop\n(Main Thread)" as EL +participant "Python\nRuntime" as PY +participant "Worker Thread" as WT +participant "GIL" as GIL +participant "OS Kernel" as OS + +== Запуск Worker Thread == + +EL -> WT: asyncio.to_thread(requests.get) +activate WT + +note over EL #90EE90 + Event Loop продолжает работать! + Ждёт своей очереди на GIL +end note + +== Фаза 1: Worker Thread стартует (Python код) == + +WT -> GIL: Захватывает GIL +note over GIL #FFD700: Worker Thread держит GIL + +WT -> WT: import requests +WT -> WT: подготовка параметров +WT -> WT: создание Request объекта + +note over EL #FFD700 + Event Loop ХОЧЕТ GIL, + но ждёт своей очереди +end note + +PY -> PY: Прошло ~5ms или 100 bytecode инструкций + +note over PY #90EE90 + **GIL SWITCHING!** + Python runtime автоматически + переключает GIL между потоками +end note + +PY -> WT: Отпусти GIL +WT -> GIL: Освобождает GIL (временно) +GIL -> EL: Захватывает GIL + +note over EL #90EE90 + Event Loop работает! + Обрабатывает задачи +end note + +EL -> EL: await task1 +EL -> EL: await task2 + +PY -> PY: Прошло ~5ms + +PY -> EL: Отпусти GIL +EL -> GIL: Освобождает GIL +GIL -> WT: Захватывает GIL + +note over WT + Worker Thread продолжает + подготовку к I/O +end note + +== Фаза 2: Системный вызов (I/O) == + +WT -> WT: socket.connect() +note over WT #90EE90: Это системный вызов! + +WT -> GIL: **Полностью освобождает GIL** +note over GIL #90EE90 + 🔓 GIL СВОБОДЕН! + Нет конкуренции +end note + +WT -> OS: socket.recv() - ждёт данные + +note over OS + Ядро читает данные + из сети + (секунды!) +end note + +par Event Loop работает БЕЗ конкуренции + EL -> GIL: Захватывает GIL + EL -> EL: await task1 ✅ + EL -> EL: await task2 ✅ + EL -> EL: await task3 ✅ + note over EL #90EE90 + Полная скорость! + GIL не занят + end note +end + +== Фаза 3: Данные получены == + +OS --> WT: Данные получены +WT -> GIL: Захватывает GIL опять +WT -> WT: обработка ответа (Python код) + +note over WT,GIL #FFD700 + Снова конкуренция за GIL + (но это быстро - десятки мс) +end note + +PY -> PY: GIL switching работает + +WT -> WT: возврат результата +WT --> EL: Результат готов +deactivate WT + +note over EL,WT #90EE90 + **Итого:** + - Фаза 1 (подготовка): ~10-50ms, GIL switching + - Фаза 2 (I/O): секунды, GIL свободен + - Фаза 3 (обработка): ~10-50ms, GIL switching + + Основное время - I/O, где GIL свободен! +end note + +@enduml + diff --git a/python/to_thread/with_to_thread.puml b/python/to_thread/with_to_thread.puml index e69de29..db6913f 100644 --- a/python/to_thread/with_to_thread.puml +++ b/python/to_thread/with_to_thread.puml @@ -0,0 +1,54 @@ +@startuml scenario2_io_with_tothread +title I/O операция С to_thread() - Event Loop свободен ✅ + +participant "Event Loop\n(Main Thread)" as EL +participant "Worker Thread" as WT +participant GIL +participant "OS Kernel" as OS + +note over EL #90EE90: Event Loop работает + +EL -> WT: asyncio.to_thread(requests.get) +activate WT + +note over EL #90EE90 + **Event Loop СВОБОДЕН!** + Обрабатывает другие задачи +end note + +group Event Loop продолжает работу + EL -> EL: await других async задач ✅ + EL -> EL: обработка новых запросов ✅ + EL -> EL: работа продолжается ✅ +end + +group Worker Thread делает I/O + WT -> GIL: Захватывает GIL + WT -> OS: socket.recv() + + note over WT,GIL #90EE90 + 🔓 **GIL ОСВОБОЖДАЕТСЯ** + (внутри C-кода requests) + Python интерпретатор ждёт OS + end note + + note over OS + Ядро читает данные + с сетевого интерфейса + end note + + OS --> WT: Данные получены + WT -> GIL: Снова захватывает GIL +end + +WT --> EL: Результат готов +deactivate WT +EL -> EL: получает результат через await + +note over EL,WT #90EE90 + **Ключевой момент:** + Пока Worker Thread ждёт OS, + Event Loop продолжает работать! + GIL освобождается во время системных вызовов +end note +@enduml