Add some python schemes
This commit is contained in:
parent
4e56f0f097
commit
62e533fec5
3 changed files with 316 additions and 0 deletions
140
python/schemes/multiprocessing.puml
Normal file
140
python/schemes/multiprocessing.puml
Normal file
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in a new issue