ЁЯз╡ рдзрдбрд╛ 08 тАФ Concurrency рдЖрдгрд┐ GIL: рдПрдХ рдмреЕрдЯрди, рдЕрдиреЗрдХ рдзрд╛рд╡рдкрдЯреВ
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 08 ┬╖ рдорд╛рдЧреЗ: lesson-07-io-batching ┬╖ рдкреБрдвреЗ: lesson-09-queueing
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ07, рдЖрдгрд┐ concurrency: рдПрдХрд╛рдЪ рд╡реЗрд│реА рдЕрдиреЗрдХ рдХрд╛рдореЗ рдХрд░рдгреЗ. рдкрд╣рд┐рд▓рд╛ рдкреНрд░рд╢реНрди рдиреЗрд╣рдореА рд╣рд╛рдЪ рдЕрд╕рддреЛ:
рдХрд╛рдо рдХрд╢рд╛рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рдд рдЖрд╣реЗ. I/O-bound рдХрд╛рдо рдЙрддреНрддрд░рд╛рдВрдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рддреЗ, рдЖрдгрд┐ threads рдХрд┐рдВрд╡рд╛
asyncio рд╣реА рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ рдПрдХрдореЗрдХрд╛рдВрд╡рд░ рдЪрдврд╡реВ (overlap) рд╢рдХрддрд╛рдд. CPU-bound рдХрд╛рдорд╛рд▓рд╛ processor рд▓рд╛рдЧрддреЛ, рдЖрдгрд┐
standard CPython рдордзреНрдпреЗ рдПрдХрд╛ рд╡реЗрд│реА рдПрдХрдЪ thread Python code рдЪрд╛рд▓рд╡рддреЛ тАФ рд╣рд╛рдЪ GIL. рддреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛
processes (рдХрд┐рдВрд╡рд╛ free-threaded build) рд▓рд╛рдЧрддрд╛рдд. perf/demo.py рдордзреАрд▓ concurrency()
рдЖрдгрд┐ perf/sim.py рдордзреАрд▓ schedule тАФ рд╣реЗ tick-by-tick рдЪрд╛рд▓рдгрд╛рд░реЗ рд╢рд┐рдХрд╡рдгреНрдпрд╛рд╕рд╛рдареАрдЪреЗ model
рдЖрд╣реЗ, рдЦрд░реЗ threads рдирд╡реНрд╣реЗрдд.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рд░рд┐рд▓реЗ рд╕рдВрдШрд╛рдд 8 рдзрд╛рд╡рдкрдЯреВ рдЖрд╣реЗрдд рдЖрдгрд┐ рдПрдХрдЪ рдмреЕрдЯрди. ЁЯев рдпрд╛ track рд╡рд░ рдзрд╛рд╡рдкрдЯреВ рдлрдХреНрдд рдмреЕрдЯрди рд╣рд╛рддрд╛рдд рдЕрд╕рддрд╛рдирд╛рдЪ рдзрд╛рд╡реВ рд╢рдХрддреЗ.
- рдкрд╛рдгреНрдпрд╛рдЪреНрдпрд╛ рдЯреЗрдмрд▓рд╛рдЪреА рдХрд╛рдореЗ (I/O-bound): рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВ 5 рдкрд╛рд╡рд▓реЗ рдЬреЙрдЧ рдХрд░рддреЗ, рдордЧ рдкрд╛рдгреНрдпрд╛рдЪреНрдпрд╛ рдЯреЗрдмрд▓рд╛рдЬрд╡рд│ рддрд┐рдЪреНрдпрд╛ рдкреЗрдпрд╛рд╕рд╛рдареА 100 рд╕реЗрдХрдВрдж рдерд╛рдВрдмрддреЗ. ЁЯЪ░ рдерд╛рдВрдмрд╛рдпрд▓рд╛ рдмреЕрдЯрди рд▓рд╛рдЧрдд рдирд╛рд╣реА! рдореНрд╣рдгреВрди рдзрд╛рд╡рдкрдЯреВ 1 рддрд┐рдЪреА 5 рдкрд╛рд╡рд▓реЗ рдЬреЙрдЧ рдХрд░рддреЗ, рдмреЕрдЯрди рдЦрд╛рд▓реА рдареЗрд╡рддреЗ, рдЖрдгрд┐ рдерд╛рдВрдмрд╛рдпрд▓рд╛ рдЬрд╛рддреЗ; рдзрд╛рд╡рдкрдЯреВ 2 рддреЗ рдЙрдЪрд▓рддреЗ, рдЖрдгрд┐ рдЕрд╕реЗрдЪ рдкреБрдвреЗ. рд╕рдЧрд│реНрдпрд╛ 8 рдЬрдгреА рдПрдХрд╛рдЪ рд╡реЗрд│реА рдерд╛рдВрдмрддрд╛рдд. рдПрдХреВрдг: рд╕реБрдорд╛рд░реЗ 140, 840 рдирд╡реНрд╣реЗ. ЁЯОЙ
- Sprint рдЪреА рдХрд╛рдореЗ (CPU-bound): рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВрд▓рд╛ 100 рдкрд╛рд╡рд▓реЗ sprint рдХрд░рд╛рд╡реЗ рд▓рд╛рдЧрддреЗ. Sprint рд▓рд╛ рдмреЕрдЯрди рд▓рд╛рдЧрддреЗ. рдПрдХрдЪ рдмреЕрдЯрди рдЕрд╕реЗрд▓ рддрд░, рдХрд┐рддреАрд╣реА рдзрд╛рд╡рдкрдЯреВ рдЕрд╕реЛрдд, sprints рдПрдХрд╛рдорд╛рдЧреВрди рдПрдХ рд╣реЛрддрд╛рдд: 800. ЁЯШй
Sprints рд╡реЗрдЧрд╡рд╛рди рдХрд╕реЗ рдХрд░рд╛рдпрдЪреЗ? рдЕрдзрд┐рдХ tracks рд╡рд╛рдкрд░рд╛, рдкреНрд░рддреНрдпреЗрдХрд╛рд╡рд░ рд╕реНрд╡рддрдГрдЪреЗ рдмреЕрдЯрди (рдЕрдиреЗрдХ cores рд╡рд░ processes). рдХрд┐рдВрд╡рд╛ рдЕрд╕рд╛ рдирд╡рд╛ track рдЬрд┐рдереЗ рдкреНрд░рддреНрдпреЗрдХ рдзрд╛рд╡рдкрдЯреВ рдПрдХрд╛рдЪ рд╡реЗрд│реА рдзрд╛рд╡реВ рд╢рдХрддреЗ (free-threaded build). рдорд╛рддреНрд░ рджреБрд╕рд▒реНрдпрд╛ tracks рд╡рд░ рдкреЛрд╣реЛрдЪрд╛рдпрд▓рд╛ рдЖрдзреА рдереЛрдбрд╛ рд╡реЗрд│ рд▓рд╛рдЧрддреЛ.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
io["ЁЯЪ░ I/O-bound ├Ч 8<br/>5 ms Python + 100 ms waiting"] --> seq1["one after another: 840 ms"]
io --> thr1["8 threads (GIL): 140 ms"]
io --> as1["asyncio: 140 ms"]
cpu["ЁЯПГ CPU-bound ├Ч 8<br/>100 ms Python"] --> seq2["one after another: 800 ms"]
cpu --> thr2["8 threads (GIL): 800 ms"]
cpu --> pr["4 processes: 250 ms"]
cpu --> ft["free-threaded, 4 cores: 200 ms"]
ЁЯЧ║я╕П рд░реЗрдЦрд╛рдЯрд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/performance/lesson-diagrams.html#l08
тЭУ рдХрд╛рдп
- Concurrency рд╡рд┐рд░реБрджреНрдз parallelism тАФ concurrency рдореНрд╣рдгрдЬреЗ рдПрдХрд╛рдЪ рд╡реЗрд│реА рдЕрдиреЗрдХ tasks рд╣рд╛рддрд╛рд│рдгреЗ (рддреНрдпрд╛рдВрдЪреНрдпрд╛рдд рдЖрд▓рдЯреВрди рдкрд╛рд▓рдЯреВрди рдЬрд╛рдгреЗ); parallelism рдореНрд╣рдгрдЬреЗ рдПрдХрд╛рдЪ рдХреНрд╖рдгреА рдЕрдиреЗрдХ tasks рдХрд░рдгреЗ (рдЕрдиреЗрдХ CPU cores). рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ рдлрдХреНрдд concurrency рдиреЗрд╣реА overlap рд╣реЛрддреЗ; рдЧрдгрдиреЗрд▓рд╛ parallelism рд▓рд╛рдЧрддреЛ.
- I/O-bound тАФ рд╡реЗрд│ рдореБрдЦреНрдпрддрдГ рд╡рд╛рдЯ рдкрд╛рд╣рдгреНрдпрд╛рдд рдЬрд╛рддреЛ (network, disk, database). CPU-bound тАФ рд╡реЗрд│ рдореБрдЦреНрдпрддрдГ рдЧрдгрдиреЗрдд рдЬрд╛рддреЛ. Profile (рдзрдбрд╛ 03) рдХрд┐рдВрд╡рд╛ рдлрдХреНрдд CPU time рдЪреА wall time рд╢реА рддреБрд▓рдирд╛ рдХреЗрд▓реНрдпрд╛рд╡рд░ рдХреЛрдгрддреЗ рддреЗ рдХрд│рддреЗ.
- GIL (global interpreter lock) тАФ standard CPython build рдордзреНрдпреЗ рдПрдХ lock рдЬреЛ рдПрдХрд╛ рд╡реЗрд│реА рдПрдХрд╛рдЪ thread
рд▓рд╛ Python bytecode рдЪрд╛рд▓рд╡реВ рджреЗрддреЛ. Thread I/O рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рдд рдЕрд╕рддрд╛рдирд╛ (sockets, files,
time.sleep) CPython рддреЛ рд╕реЛрдбрддреЛ, рдЖрдгрд┐ рдЕрдиреЗрдХ C extensions рд▓рд╛рдВрдм рдЧрдгрдиреЗрджрд░рдореНрдпрд╛рди рддреЛ рд╕реЛрдбрддрд╛рдд (рдЙрджрд╛. рдореЛрдареНрдпрд╛ data рд╡рд░hashlib, рдЕрдиреЗрдХ operations рдордзреНрдпреЗ NumPy). рддреЛ рдзрд░реВрди рдареЗрд╡рд▓реЗрд▓реНрдпрд╛ thread рд▓рд╛ switch interval рдирдВрддрд░ (sys.getswitchinterval(), default 5 ms) рддреЛ рд╕реЛрдбрд╛рдпрд▓рд╛ рд╕рд╛рдВрдЧрд┐рддрд▓реЗ рдЬрд╛рддреЗ. - Threads (
threading,concurrent.futures.ThreadPoolExecutor) тАФ memory share рдХрд░рддрд╛рдд; I/O overlap рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЙрддреНрддрдо; standard build рдордзреНрдпреЗ рд╢реБрджреНрдз-Python CPU рдХрд╛рдо рд╡реЗрдЧрд╡рд╛рди рдХрд░рдд рдирд╛рд╣реАрдд. - asyncio тАФ рдПрдХ thread, рдЕрдиреЗрдХ tasks рдЬреЗ рдкреНрд░рддреНрдпреЗрдХ
awaitрд╡рд░ рдирд┐рдпрдВрддреНрд░рдг рд╕реЛрдбрддрд╛рдд. рдкреНрд░рддреНрдпреЗрдХ task рдЦреВрдк рд╕реНрд╡рд╕реНрдд, рд╣рдЬрд╛рд░реЛ рдерд╛рдВрдмрд▓реЗрд▓реНрдпрд╛ connections рд╕рд╛рдареА рдЪрд╛рдВрдЧрд▓реЗ. Coroutine рдордзреАрд▓ CPU-рдЬрдб function рд╕рдВрдкреЗрдкрд░реНрдпрдВрдд рдкреНрд░рддреНрдпреЗрдХ task рдЕрдбрд╡рддреЗ (рддреЗasyncio.to_threadрдХрд┐рдВрд╡рд╛ process pool рдиреЗ рдмрд╛рд╣реЗрд░ рд╣рд▓рд╡рд╛). - Processes (
multiprocessing,ProcessPoolExecutor) тАФ рд╡реЗрдЧрд╡реЗрдЧрд│реЗ interpreters, рдкреНрд░рддреНрдпреЗрдХрд╛рдЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ GIL, рдореНрд╣рдгреВрди CPU рдХрд╛рдо рдЕрдиреЗрдХ cores рд╡рд░ parallel рдЪрд╛рд▓рддреЗ. рдЦрд░реНрдЪ: processes рд╕реБрд░реВ рдХрд░рдгреЗ, рдЖрдгрд┐ arguments рд╡ рдирд┐рдХрд╛рд▓ processes рджрд░рдореНрдпрд╛рди рдкрд╛рдард╡рдгреНрдпрд╛рд╕рд╛рдареА рддреНрдпрд╛рдВрдЪреЗ pickling. - Free-threaded CPython тАФ GIL рд╢рд┐рд╡рд╛рдпрдЪрд╛ build (PEP 703). рддреЛ Python 3.13 рдордзреНрдпреЗ experimental
рдореНрд╣рдгреВрди рдЖрд▓рд╛ (рдмрд╣реБрдзрд╛
python3.13tрдореНрд╣рдгреВрди install рд╣реЛрддреЛ) рдЖрдгрд┐ 3.14 рдордзреНрдпреЗ рдЕрдзрд┐рдХреГрддрдкрдгреЗ supported, рдкрдг рдЕрдЬреВрдирд╣реА optional рдЭрд╛рд▓рд╛ (PEP 779). Default download рдордзреНрдпреЗ рдЕрдЬреВрдирд╣реА GIL рдЖрд╣реЗ.sys._is_gil_enabled()(3.13+) рддреБрдореНрд╣реА рдХреЛрдгрддрд╛ рдЪрд╛рд▓рд╡рдд рдЖрд╣рд╛рдд рддреЗ рд╕рд╛рдВрдЧрддреЛ; support рдЬрд╛рд╣реАрд░ рди рдХреЗрд▓реЗрд▓рд╛ C extension import рдХреЗрд▓реНрдпрд╛рд╕ GIL рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓реВ рд╣реЛрдК рд╢рдХрддреЛ.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг "threads рд╡рд╛рдврд╡рд╛" рд╣рд╛ performance рдЪрд╛ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рд╕рд▓реНрд▓рд╛ рдЖрд╣реЗ рдЖрдгрд┐ рддреЛ рдЕрдиреЗрдХрджрд╛ рдЪреБрдХреАрдЪрд╛ рдЕрд╕рддреЛ. Web scraper рд╕рд╛рдареА threads рдХрд┐рдВрд╡рд╛ asyncio рдореЛрдареА speed-up рджреЗрдК рд╢рдХрддрд╛рдд. рд╢реБрджреНрдз Python рдордзрд▓реНрдпрд╛ рдЖрдХрдбреЗрдореЛрдбреАрдЪреНрдпрд╛ loop рд╕рд╛рдареА, standard build рдордзрд▓реЗ threads рдХрд╛рд╣реАрдЪ speed-up рджреЗрдд рдирд╛рд╣реАрдд, рдЖрдгрд┐ processes рдкреНрд░рддреНрдпреЗрдХреА рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд рдПрдХрд╛ core рдЗрддрдХрд╛ рдлрд╛рдпрджрд╛ рджреЗрддрд╛рдд тАФ start-up рдЖрдгрд┐ data copying рд╡рдЬрд╛ рдХрд░реВрди. рд╡рд╛рдЯ рдкрд╛рд╣рдгреНрдпрд╛рдЪреНрдпрд╛ рдкреНрд░рдХрд╛рд░рд╛рдиреБрд╕рд╛рд░ рдирд┐рд╡рдбрд▓реНрдпрд╛рдиреЗ рдЦреВрдк рд╡рд╛рдпрд╛ рдЬрд╛рдгрд╛рд░реЗ рдХрд╛рдо рд╡рд╛рдЪрддреЗ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
perf/sim.py рдордзреАрд▓ schedule(tasks, workers, cpu_slots, start_ms) рдПрдХ tick-by-tick
model рдЪрд╛рд▓рд╡рддреЗ: рдкреНрд░рддреНрдпреЗрдХ task рдореНрд╣рдгрдЬреЗ phases рдЪреА рдпрд╛рджреА, ("cpu", ms) рдХрд┐рдВрд╡рд╛ ("io", ms). рдПрдХрд╛ рд╡реЗрд│реА
рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд workers tasks рдЪрд╛рд▓реВ рдЕрд╕рддрд╛рдд; рд╡рд╛рдЯ рдкрд╛рд╣рдгреНрдпрд╛рд▓рд╛ (io) CPU рд▓рд╛рдЧрдд рдирд╛рд╣реА; рдПрдХрд╛рдЪ millisecond рдордзреНрдпреЗ
рдлрдХреНрдд cpu_slots tasks рдЧрдгрдирд╛ рдХрд░реВ рд╢рдХрддрд╛рдд тАФ GIL рд╕рд╣ threads рдХрд┐рдВрд╡рд╛ asyncio рд╕рд╛рдареА 1, processes рдХрд┐рдВрд╡рд╛
free-threaded build рд╕рд╛рдареА cores рдЪреА рд╕рдВрдЦреНрдпрд╛ тАФ round-robin рдкрджреНрдзрддреАрдиреЗ рд╡рд╛рдЯреВрди. рдкреНрд░рддреНрдпреЗрдХ process рдПрдХрджрд╛рдЪ
start_ms рдореЛрдЬрддреЗ. рдЦрд░реЗ CPython рдкреНрд░рддреНрдпреЗрдХ millisecond рд▓рд╛ рдирд╡реНрд╣реЗ рддрд░ рджрд░ рдХрд╛рд╣реА milliseconds рд▓рд╛ switch рдХрд░рддреЗ,
рдЖрдгрд┐ рдЦрд▒реНрдпрд╛ I/O рд╡ start-up рдЪреНрдпрд╛ рд╡реЗрд│рд╛ рдмрджрд▓рдд рдЕрд╕рддрд╛рдд; рдкреБрдвреЗ рдЯрд┐рдХрддреЛ рддреЛ рдирд┐рдХрд╛рд▓рд╛рдВрдЪрд╛ рдЖрдХрд╛рд░.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 perf/demo.py concurrency
python3 - <<'EOF'
import sys; sys.path.insert(0, "perf"); from sim import schedule
mixed = [[("cpu", 20), ("io", 80)]] * 8
print("8 mixed tasks (20 ms Python + 80 ms waiting):")
for label, w, slots, start in (("one after another", 1, 1, 0), ("8 threads, GIL", 8, 1, 0), ("4 processes", 4, 4, 50), ("8 processes", 8, 4, 50)):
print(f" {label:<18} {schedule(mixed, w, slots, start):>4} ms")
for n in (2, 4, 8, 16):
print(f"CPU-bound ├Ч 8 on {n:>2} threads with the GIL: {schedule([[('cpu', 100)]] * 8, n, 1)} ms")
EOF
python3 - <<'EOF'
import time, threading
def wait(): time.sleep(0.1)
t0 = time.perf_counter(); [wait() for _ in range(8)]; seq = time.perf_counter() - t0
ts = [threading.Thread(target=wait) for _ in range(8)]
t0 = time.perf_counter(); [t.start() for t in ts]; [t.join() for t in ts]; thr = time.perf_counter() - t0
print(f"real: 8 sleeps of 0.1 s тЖТ one after another {seq:.2f} s ┬╖ 8 threads {thr:.2f} s (your numbers will differ)")
EOF
рд╢реЗрд╡рдЯрдЪрд╛ snippet рдЦрд░реЗ threads рдЖрдгрд┐ рдЦрд░реЗ рдШрдбреНрдпрд╛рд│ рд╡рд╛рдкрд░рддреЛ тАФ рд╕рд╛рдзрд╛рд░рдг 0.82 s рд╡рд┐рд░реБрджреНрдз 0.11 s.
рддреБрдордЪреЗ рдЖрдХрдбреЗ рдереЛрдбреЗ рд╡реЗрдЧрд│реЗ рдЕрд╕рддреАрд▓; 8├Ч рдЪрд╛ рдЖрдХрд╛рд░ рдмрджрд▓рдгрд╛рд░ рдирд╛рд╣реА, рдХрд╛рд░рдг sleep GIL рд╕реЛрдбрддреЛ.
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
concurrency рд╣реЗ рдЫрд╛рдкрддреЗ:
тФАтФА 8 tasks ┬╖ I/O-bound: 5 ms of Python + 100 ms waiting for a reply ┬╖ CPU-bound: 100 ms of Python (a teaching model)
I/O-bound CPU-bound
one after another 840 ms 800 ms
8 threads, one baton (the GIL) 140 ms 800 ms
asyncio, 1 thread, 8 tasks 140 ms 800 ms
4 processes on 4 cores (+50 ms start) 260 ms 250 ms
8 threads, free-threaded build, 4 cores 110 ms 200 ms
рддреБрдордЪрд╛ рдкрд╣рд┐рд▓рд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:
8 mixed tasks (20 ms Python + 80 ms waiting):
one after another 800 ms
8 threads, GIL 240 ms
4 processes 250 ms
8 processes 170 ms
CPU-bound ├Ч 8 on 2 threads with the GIL: 800 ms
CPU-bound ├Ч 8 on 4 threads with the GIL: 800 ms
CPU-bound ├Ч 8 on 8 threads with the GIL: 800 ms
CPU-bound ├Ч 8 on 16 threads with the GIL: 800 ms
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
GIL рд╕рд╣, CPU рдХрд╛рдорд╛рд▓рд╛ 2 threads рдЕрд╕реЛрдд рдХреА 16, 800 ms рд▓рд╛рдЧрд▓реЗ тАФ Python рдХрд╛рдо рдлрдХреНрдд рдЖрд│реАрдкрд╛рд│реАрдиреЗ рд╣реЛрддреЗ. рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ рдкреВрд░реНрдгрдкрдгреЗ overlap рдЭрд╛рд▓реЗ: 840 тЖТ 140 ms. Processes рдиреА CPU рдЪреА рд╢рд░реНрдпрдд рдЬрд┐рдВрдХрд▓реА (250 ms) рдкрдг I/O рдЪреА рд╢рд░реНрдпрдд рд╣рд░рд▓реА (260 ms рд╡рд┐рд░реБрджреНрдз 140 ms), рдХрд╛рд░рдг рддреНрдпрд╛рдВрдЪрд╛ start-up рдЦрд░реНрдЪ рдЖрдгрд┐ рдлрдХреНрдд 4 workers. рдорд┐рд╢реНрд░ рдХрд╛рдорд╛рдд, GIL рдореБрд│реЗ 8 ├Ч 20 ms = 160 ms Python рдПрдХрд╛рдЪ рд░рд╛рдВрдЧреЗрдд рд░рд╛рд╣рд┐рд▓реЗ, рдЕрдзрд┐рдХ рдПрдХ 80 ms рдЪреА рдкреНрд░рддреАрдХреНрд╖рд╛: 240 ms. рддреБрдордЪреЗ рдХрд╛рдо рдХрд╢рд╛рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рддреЗ рддреЗ рдУрд│рдЦрд╛, рдордЧ рдирд┐рд╡рдбрд╛.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- standard build рд╡рд░ рд╢реБрджреНрдз-Python CPU рдХрд╛рдорд╛рд╕рд╛рдареА threads, speed-up рдЪреА рдЕрдкреЗрдХреНрд╖рд╛ рдареЗрд╡реВрди
async defрдордзреНрдпреЗ CPU-рдЬрдб рдХрд┐рдВрд╡рд╛ blocking call (requests.get,time.sleep) тАФ рдмрд╛рдХреА рдкреНрд░рддреНрдпреЗрдХ task рдерд╛рдВрдмрддреЛ- рдкреНрд░рддреНрдпреЗрдХ рдЫреЛрдЯреНрдпрд╛ task рд╕рд╛рдареА рдПрдХ process тАФ start-up рдЖрдгрд┐ pickling рдЪрд╛ рдЦрд░реНрдЪ рдХрд╛рдорд╛рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рд╣реЛрддреЛ
- рдкреНрд░рддреНрдпреЗрдХ call рд╡рд░ worker processes рдирд╛ рдореЛрдареЗ objects рдкрд╛рдард╡рдгреЗ
- "GIL рдореБрд│реЗ рдорд╛рдЭрд╛ code thread-safe рд╣реЛрддреЛ" тАФ рд╣реЛрдд рдирд╛рд╣реА; рджреЛрди threads рдордзреВрди
count += 1рдХреЗрд▓реНрдпрд╛рд╕ рдЕрдЬреВрдирд╣реА updates рд╣рд░рд╡реВ рд╢рдХрддрд╛рдд, рдореНрд╣рдгреВрди locks рдХрд┐рдВрд╡рд╛ queues рд╡рд╛рдкрд░рд╛ - рдкреНрд░рддреНрдпреЗрдХ Python рд▓рд╛ GIL рдЖрд╣реЗ, рдХрд┐рдВрд╡рд╛ рдХреЛрдгрд╛рд▓рд╛рдЪ рдирд╛рд╣реА, рдЕрд╕реЗ рдЧреГрд╣реАрдд рдзрд░рдгреЗ тАФ 3.13+ рд╡рд░
sys._is_gil_enabled()рддрдкрд╛рд╕рд╛
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ machine рд╡рд░ тАФ standard library рддрд┐рдиреНрд╣реА рдкреНрд░рдХрд╛рд░ рд╣рд╛рддрд╛рд│рддреЗ:
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import asyncio
with ThreadPoolExecutor(max_workers=16) as pool: # I/O-bound: overlap the waits
pages = list(pool.map(fetch_page, urls))
with ProcessPoolExecutor() as pool: # CPU-bound: one process per core
scores = list(pool.map(score_heat, heats, chunksize=64))
async def main(): # thousands of waiting connections
return await asyncio.gather(*(fetch_async(u) for u in urls))
asyncio.run(main())
рддреБрдордЪреНрдпрд╛рдХрдбреЗ рдХреЛрдгрддрд╛ interpreter рдЖрд╣реЗ рддреЗ рддрдкрд╛рд╕рд╛, рдЖрдгрд┐ free-threaded build (3.13+) рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рд╛:
python3 -c "import sys; print(sys.version); print('GIL enabled:', getattr(sys, '_is_gil_enabled', lambda: True)())"
python3.13t -X gil=0 my_cpu_job.py # a free-threaded build, if installed; PYTHON_GIL=0 does the same
Web service рд╕рд╛рдареА server рдЪреЗ worker model рд╣реЗ рддреБрдордЪреНрдпрд╛рд╕рд╛рдареА рдард░рд╡рддреЗ тАФ рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде рдЕрдиреЗрдХ worker processes (parallel CPU) рдЕрд╕рд▓реЗрд▓реЗ Gunicorn, рдкреНрд░рддреНрдпреЗрдХрд╛рдд threads рдХрд┐рдВрд╡рд╛ async loop (I/O overlap рдХрд░рдгрд╛рд░реЗ):
gunicorn app:app --workers 4 --threads 8
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: process рдЪреНрдпрд╛ CPU time рдЪреА рддреНрдпрд╛рдЪреНрдпрд╛ wall time рд╢реА рддреБрд▓рдирд╛ рдХрд░рд╛. рдЬрд░ рдПрдХрд╛ core рд╡рд░ CPU тЙИ wall рдЕрд╕реЗрд▓, рддрд░ рддреЗ CPU-bound рдЖрд╣реЗ: processes, рдЪрд╛рдВрдЧрд▓реЗ algorithms, рдХрд┐рдВрд╡рд╛ native code рдЪрд╛ рд╡рд┐рдЪрд╛рд░ рдХрд░рд╛. рдЬрд░ CPU тЙк wall рдЕрд╕реЗрд▓, рддрд░ рддреЗ рд╡рд╛рдЯ рдкрд╛рд╣рдд рдЖрд╣реЗ: рдкреНрд░рддреАрдХреНрд╖рд╛ overlap рдХрд░рд╛, рддреНрдпрд╛рдВрдЪреЗ batching рдХрд░рд╛ (рдзрдбрд╛ 07), рдХрд┐рдВрд╡рд╛ cache рдХрд░рд╛ (рдзрдбрд╛ 06).
тПня╕П рдкреБрдвреЗ
рдПрдХрд╛рдЪ рдкрд╛рдгреНрдпрд╛рдЪреНрдпрд╛ рдЯреЗрдмрд▓рд╛рдЪрд╛ рд╡рд╛рдкрд░ рдЕрдзрд┐рдХ рдзрд╛рд╡рдкрдЯреВ рдХрд░реВ рд▓рд╛рдЧрд▓реНрдпрд╛ рдХреА рдПрдХ рдирд╡реА рд╕рдорд╕реНрдпрд╛ рджрд┐рд╕рддреЗ: рд░рд╛рдВрдЧ. рдкреБрдвреЗ: utilisation рд╡рд┐рд░реБрджреНрдз рдкреНрд░рддреАрдХреНрд╖реЗрдЪрд╛ рд╡реЗрд│, Little рдЪрд╛ рдирд┐рдпрдо, рдЖрдгрд┐ Amdahl рдЪрд╛ рд╕рд░реНрд╡рд╛рдд рд╣рд│реВ рдзрд╛рд╡рдкрдЯреВ.
git checkout lesson-09-queueing