<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>concurrent.futures - DataTalks.RU. Data Engineering / DWH / Data Pipeline</title>
	<atom:link href="https://datatalks.ru/tag/concurrent-futures/feed/" rel="self" type="application/rss+xml" />
	<link>https://datatalks.ru/tag/concurrent-futures/</link>
	<description>RoadMap для инженера данных. Дорожная карта по инструментам Data Engineer</description>
	<lastBuildDate>Sun, 25 Jan 2026 06:13:27 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://datatalks.ru/wp-content/uploads/2024/12/cropped-logo_datatalks-32x32.png</url>
	<title>concurrent.futures - DataTalks.RU. Data Engineering / DWH / Data Pipeline</title>
	<link>https://datatalks.ru/tag/concurrent-futures/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Python &#8212; Многозадачность, конкурентность и асинхронность</title>
		<link>https://datatalks.ru/python-threading-multiprocessing-asyncio/</link>
					<comments>https://datatalks.ru/python-threading-multiprocessing-asyncio/#respond</comments>
		
		<dc:creator><![CDATA[Data Engineer (Admin)]]></dc:creator>
		<pubDate>Fri, 26 Dec 2025 19:05:32 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[asyncio]]></category>
		<category><![CDATA[concurrent.futures]]></category>
		<category><![CDATA[multiprocessing]]></category>
		<category><![CDATA[threading]]></category>
		<guid isPermaLink="false">https://datatalks.ru/?p=2596</guid>

					<description><![CDATA[<p>Подборка материалов для освоения темы многозадачности в Python YouTube ролики Как работает GIL в Python. Многопоточность. Многопроцессность. IO/CPU-Bound Yandex for Developers &#8212; 01. Устройство CPython – Егор Овчаренко [ZProger] Многопоточность и Многопроцессорность Python. Threading &#38; Multiprocessing Python Асинхронность, многопоточность, многопроцессность в python &#124; Библиотека asyncio и асинхронный код Threading. Кратко про Python Плейлист Асинхронность в [&#8230;]</p>
<p>Сообщение <a href="https://datatalks.ru/python-threading-multiprocessing-asyncio/">Python &#8212; Многозадачность, конкурентность и асинхронность</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1>Подборка материалов для освоения темы многозадачности в Python</h1>
<h2>YouTube ролики</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=hkWmKQqLT4k" target="_blank" rel="noopener">Как работает GIL в Python. Многопоточность. Многопроцессность. IO/CPU-Bound</a></li>
<li><a href="https://www.youtube.com/watch?v=PxIqLgjtQ5Y" target="_blank" rel="noopener">Yandex for Developers &#8212; 01. Устройство CPython – Егор Овчаренко</a></li>
<li><a href="https://www.youtube.com/playlist?list=PL6plRXMq5RAAb9gwGqmgAoA-KIr-7CMuz" target="_blank" rel="noopener">[ZProger] Многопоточность и Многопроцессорность Python. Threading &amp; Multiprocessing Python</a></li>
<li><a href="https://www.youtube.com/watch?v=_4QY1nGFRY8" target="_blank" rel="noopener">Асинхронность, многопоточность, многопроцессность в python | Библиотека asyncio и асинхронный код</a></li>
<li><a href="https://www.youtube.com/watch?v=_JCV3deaFvE" target="_blank" rel="noopener">Threading. Кратко про Python</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLlWXhlUMyooawilqK4lPXRvxtbYiw34S8" target="_blank" rel="noopener">Плейлист Асинхронность в Python</a></li>
<li><a href="https://www.youtube.com/live/sCPMwXPggis" target="_blank" rel="noopener">Young&amp;&amp;Yandex ШБР 2023 — Асинхронное программирование (Python)</a> &#8212; полный плейлист <a href="https://www.youtube.com/playlist?list=PLZvfMc-lVSSPZ_VYTK8XEkZ_S_bCfyu8C" target="_blank" rel="noopener">Python ШБР 2023</a></li>
<li><a href="https://www.youtube.com/watch?v=DvVhG8-HMSQ" target="_blank" rel="noopener">Особенности asyncio.wait_for() в асинхронном Python. Как работает таймаут для корутины</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLz8SX0iNPyAIHH3xtwrcxI5UWleLB5el_" target="_blank" rel="noopener">Плейлист Асинхронность в Python</a></li>
<li><a href="https://www.youtube.com/watch?v=o_COfPdWAPw" target="_blank" rel="noopener">Асинхронное программирование на примере Python / asyncio</a></li>
<li><a href="https://www.youtube.com/watch?v=BoazgBZ4D7k" target="_blank" rel="noopener">Собеседование Python. Разбор вопросов</a></li>
<li><a href="https://www.youtube.com/watch?v=dvfnYkEHmdA" target="_blank" rel="noopener">Денис Аникин. Вновь ускоряем cpu-bound задачи</a></li>
<li><a href="https://www.youtube.com/watch?v=G2EG-eCHOiI" target="_blank" rel="noopener">Python: Threads, GIL, asyncio</a></li>
<li><a href="https://www.youtube.com/watch?v=QitEF7Qvi4w" target="_blank" rel="noopener">Лекция Тимофей Хирьянов &#8212; Параллельное программирование на Python</a></li>
<li>Yandex Developer (плейлист <a href="https://www.youtube.com/playlist?list=PLQC2_0cDcSKBHamFYA6ncnc_fYuEQUy0s" target="_blank" rel="noopener">Школа бэкенд-разработки 2019</a>) &#8212; Асинхронное программирование &#8212; <a href="https://www.youtube.com/watch?v=AXkOli6BsBY" target="_blank" rel="noopener">Лекция 1</a>, <a href="https://www.youtube.com/watch?v=IB4bJqmfjI0" target="_blank" rel="noopener">Лекция 2</a>, <a href="https://www.youtube.com/watch?v=FFUYf8FHDlY" target="_blank" rel="noopener">Лекция 3</a>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLQC2_0cDcSKCMKnywAS8eI_EgCcE3yx0r" target="_blank" rel="noopener">Плейлист Школа бэкенд-разработки 2021</a></li>
</ul>
</li>
<li><a href="https://www.youtube.com/playlist?list=PLlKID9PnOE5ibKy6U7XaCA2Nqk_R1d5CJ" target="_blank" rel="noopener">Плейлист &#171;Конкурентность в Python&#187;</a></li>
<li><a href="https://www.youtube.com/watch?v=AWX4JnAnjBE" target="_blank" rel="noopener">GIL в Python: зачем он нужен и как с этим жить</a></li>
<li><a href="https://www.youtube.com/watch?v=z7WIm0iZcOU" target="_blank" rel="noopener">Асинхронный Python-код медленнее обычного кода! Ааа!!1один. Aiohttp VS синхронные фреймворки</a></li>
</ul>
<p><strong>YouTube English:</strong></p>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLhNSoGM2ik6SIkVGXWBwerucXjgP1rHmB" target="_blank" rel="noopener">PlayList: Воспроизвести все import asyncio: Learn Python&#8217;s AsyncIO</a></li>
<li><a href="https://www.youtube.com/watch?v=Wsv07g4ml8I" target="_blank" rel="noopener">CPU Bound vs. I/O Bound | Computer Basics</a></li>
<li><a href="https://www.youtube.com/watch?v=AZnGRKFUU0c" target="_blank" rel="noopener">threading vs multiprocessing in python</a></li>
<li><a href="https://www.youtube.com/watch?v=XbBFKco43aw" target="_blank" rel="noopener">I/OBound vs CPU Bound Code</a></li>
</ul>
<h2>Статьи</h2>
<ul>
<li><a href="https://habr.com/ru/companies/otus/articles/960206/" target="_blank" rel="noopener">CPython простыми словами: всё, что нужно знать начинающему</a></li>
<li><a href="https://habr.com/ru/companies/otus/articles/769448/" target="_blank" rel="noopener">Как устроен GIL (Global Interpreter Lock) в Python: влияние на многозадачность и производительность</a></li>
<li><a href="https://habr.com/ru/articles/84629/" target="_blank" rel="noopener">Как устроен GIL в Python</a></li>
<li><a href="https://habr.com/ru/companies/wunderfund/articles/586360/" target="_blank" rel="noopener">Глобальная блокировка интерпретатора (GIL) и её воздействие на многопоточность в Python</a></li>
<li><a href="https://habr.com/ru/articles/417215/" target="_blank" rel="noopener">Всё, что нужно знать о сборщике мусора в Python</a></li>
<li><a href="https://habr.com/ru/companies/ntechlab/articles/946098/" target="_blank" rel="noopener">Визуализация управления памятью в Python: что творится внутри?</a></li>
</ul>
<h1>Введение в Python</h1>
<h2>Исходный глоссарий</h2>
<h3>Виртуальное адресное пространство</h3>
<p><strong>Виртуальное адресное пространство</strong> — это абстракция, предоставляемая ОС, в рамках которой каждый процесс видит собственную непрерывную адресную память, не зная о реальном физическом расположении данных.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/Virtual_address_space.jpg"><img fetchpriority="high" decoding="async" class="aligncenter size-full wp-image-2627" src="https://datatalks.ru/wp-content/uploads/2025/12/Virtual_address_space.jpg" alt="" width="660" height="452" srcset="https://datatalks.ru/wp-content/uploads/2025/12/Virtual_address_space.jpg 660w, https://datatalks.ru/wp-content/uploads/2025/12/Virtual_address_space-300x205.jpg 300w, https://datatalks.ru/wp-content/uploads/2025/12/Virtual_address_space-450x308.jpg 450w" sizes="(max-width: 660px) 100vw, 660px" /></a></p>
<p><strong>Структура виртуального адресного пространства</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">Высокие адреса
┌─────────────────────────┐
│ Kernel space (отображ.) │  ← недоступен напрямую
├─────────────────────────┤
│ Stack                   │  ← стек потоков
├─────────────────────────┤
│ Heap                    │  ← объекты Python
├─────────────────────────┤
│ Data / BSS              │  ← глобальные переменные
├─────────────────────────┤
│ Code (text segment)     │  ← байткод + C-расширения
└─────────────────────────┘
Низкие адреса</pre><p>Python не управляет адресным пространством напрямую — он запрашивает память у ОС через <code>malloc</code>, <code>mmap</code>, <code>brk</code>.</p>
<h3>Heap &amp; Stack</h3>
<p><strong>Стек(stack)</strong> и <strong>куча(heap)</strong> – области в оперативной памяти (ОЗУ, RAM), в которых хранятся данные приложения во время его выполнения. Управление оперативной памятью для приложения Python осуществляется с помощью <strong>Python memory manager</strong>.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/python_memory_management.jpeg"><img decoding="async" class="aligncenter size-full wp-image-2628" src="https://datatalks.ru/wp-content/uploads/2025/12/python_memory_management.jpeg" alt="" width="764" height="529" srcset="https://datatalks.ru/wp-content/uploads/2025/12/python_memory_management.jpeg 764w, https://datatalks.ru/wp-content/uploads/2025/12/python_memory_management-300x208.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/python_memory_management-450x312.jpeg 450w" sizes="(max-width: 764px) 100vw, 764px" /></a></p>
<p><strong>В управлении памятью (Python memory management)</strong> существует <strong>механизм учёта ссылок (reference counting)</strong>, который ведет внутренний журнал того, как много ссылок ссылается на объект в куче. Когда на объект не ссылается ни одна ссылка <strong>сборщик мусора (Garbage collector)</strong> автоматически освобождает память выделенную ранее для этого объекта.</p>
<p><strong>Heap</strong> — область памяти процесса, предназначенная для динамического выделения памяти во время выполнения.</p>
<p><strong>В Python:</strong></p>
<ul>
<li>все объекты Python живут в heap</li>
<li><code>int</code>, <code>list</code>, <code>dict</code>, <code>class</code>, <code>function</code> — всё heap</li>
</ul>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack.jpeg"><img decoding="async" class="aligncenter size-full wp-image-2629" src="https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack.jpeg" alt="" width="971" height="515" srcset="https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack.jpeg 971w, https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack-300x159.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack-768x407.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack-450x239.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/python_heap_stack-780x414.jpeg 780w" sizes="(max-width: 971px) 100vw, 971px" /></a></p>
<p><strong>Stack</strong> — область памяти, используемая для хранения локальных переменных, адресов возврата, аргументов функций. Каждый поток имеет собственный стек.</p>
<p><strong>Python stack</strong> — это логическая абстракция, а не «настоящий» stack ОС.</p>
<h3>Дескрипторы ресурсов (File Descriptors)</h3>
<p><strong>File descriptor</strong> — это целое число, которое операционная система даёт твоей программе, когда она открывает файл или другое устройство ввода-вывода (например, сокет, pipe). Это как минимальный идентификатор ресурса: Python использует его для низкоуровневых операций с файлами.</p>
<ul>
<li>Это не объект Python, это число, под которым ОС видит открытый файл/ресурс.</li>
<li>С помощью FD можно делать низкоуровневые операции (чтение, запись, дупликация, перемещение позиции и т.п.).</li>
<li>Отличие от обычного open() в том, что FD используют функции модуля os, а не методы объекта файла.</li>
</ul>
<p>Каждый <strong>FD</strong> — ограниченный ресурс. Если ты открыл много файлов или сокетов и не закрыл их, система закончится и новые операции упадут с ошибками вроде Too many open files. Это особенно критично для серверов, которые держат много соединений одновременно.</p>
<p>В Unix-системах всё представляется как файл. Стандартные дескрипторы:</p>
<ul>
<li><code>0</code> — stdin</li>
<li><code>1</code> — stdout</li>
<li><code>2</code> — stderr</li>
</ul>
<p>Ты можешь перенаправлять их (например, в скриптах bash или в приложениях), и это тоже работает через FD.</p>
<p><strong>Примеры ресурсов:</strong> файлы, сокеты, pipe, eventfd, epoll/kqueue</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2630" src="https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux.jpeg" alt="" width="862" height="591" srcset="https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux.jpeg 862w, https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux-300x206.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux-768x527.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux-450x309.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/file_descriptor_linux-780x535.jpeg 780w" sizes="(max-width: 862px) 100vw, 862px" /></a></p>
<h3>Глобальные переменные в Python</h3>
<p>Глобальные переменные — это имена, привязанные в namespace модуля.</p>
<p>В реальности:</p>
<ul>
<li>имя <code>x</code> → указатель</li>
<li>объект <code>10</code> → heap</li>
<li>namespace модуля → dict в heap</li>
</ul>
<h3>Регистры CPU</h3>
<p><strong>Регистры CPU</strong> — сверхбыстрая память внутри процессора.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/cpu_regestry.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2631" src="https://datatalks.ru/wp-content/uploads/2025/12/cpu_regestry.png" alt="" width="627" height="368" srcset="https://datatalks.ru/wp-content/uploads/2025/12/cpu_regestry.png 627w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_regestry-300x176.png 300w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_regestry-450x264.png 450w" sizes="(max-width: 627px) 100vw, 627px" /></a></p>
<p><strong>Хранят: </strong>указатель инструкции (<strong>IP</strong>), указатель стека (<strong>SP</strong>), флаги, временные значения.</p>
<p>Python не управляет регистрами напрямую. Но при <strong>context switch ОС</strong> сохраняет регистры, при переключении потоков Python → регистры меняются. Это основная стоимость <strong>context switch</strong>.</p>
<h3>User Space</h3>
<p><strong>User space</strong> — режим выполнения с ограниченными правами.</p>
<p>Python-код выполняется исключительно в user space.</p>
<p><strong>Запрещено:</strong></p>
<ul>
<li>прямой доступ к устройствам</li>
<li>управление памятью</li>
<li>прерывания</li>
</ul>
<h3>Kernel Space</h3>
<p><strong>Kernel space</strong> — привилегированный режим выполнения.</p>
<p><strong>Ядро:</strong></p>
<ul>
<li>управляет памятью</li>
<li>планирует процессы</li>
<li>обрабатывает I/O</li>
<li>управляет сетевым стеком</li>
</ul>
<h2>Что такое процесс, поток, системный вызов и context switch?</h2>
<p><strong>Процесс</strong> — это изолированное выполняемое окружение, предоставляемое ОС. Каждый процесс имеет собственное виртуальное адресное пространство, heap, stack, дескрипторы ресурсов (файлы, сокеты).</p>
<p><strong>Поток (Thread)</strong> &#8212; это единица выполнения внутри процесса. Потоки разделяют одно адресное пространство процесса, каждый поток имеет собственный stack, выполняются псевдопараллельно внутри 1 процесса. Общее у потоков heap, глобальные переменные, объекты Python. Раздельное &#8212; stack, регистры CPU.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/multiprocessing_threading.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2635" src="https://datatalks.ru/wp-content/uploads/2025/12/multiprocessing_threading.jpeg" alt="" width="772" height="959" srcset="https://datatalks.ru/wp-content/uploads/2025/12/multiprocessing_threading.jpeg 772w, https://datatalks.ru/wp-content/uploads/2025/12/multiprocessing_threading-242x300.jpeg 242w, https://datatalks.ru/wp-content/uploads/2025/12/multiprocessing_threading-768x954.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/multiprocessing_threading-450x559.jpeg 450w" sizes="(max-width: 772px) 100vw, 772px" /></a></p>
<p><strong>Системный вызов</strong> — это контролируемый переход из <strong>user space</strong> в <strong>kernel space</strong>.</p>
<p>Python-код не может напрямую:</p>
<ul>
<li>читать диск</li>
<li>писать в сокет</li>
<li>создавать процесс</li>
<li>спать</li>
</ul>
<p><strong>Что происходит при системном вызове:</strong></p>
<ul>
<li>Python вызывает <strong>C-функцию</strong></li>
<li><strong>C-функция</strong> делает <code>syscall</code></li>
<li>ОС выполняет операцию</li>
<li>Поток блокируется, пока ОС не закончит</li>
</ul>
<p><strong>В этот момент:</strong></p>
<ul>
<li><strong>GIL</strong> может быть освобождён</li>
<li>другой поток может выполняться</li>
</ul>
<p><strong>Context switch</strong> — это переключение CPU с одной задачи на другую.</p>
<p><strong>Бывает:</strong></p>
<ul>
<li>между потоками</li>
<li>между процессами</li>
</ul>
<p>Что сохраняется:</p>
<ul>
<li>регистры CPU</li>
<li>указатель стека</li>
<li>состояние планировщика</li>
</ul>
<h2>Архитектура CPython</h2>
<p><strong>CPython</strong> — это эталонная реализация языка программирования Python. Это версия Python по умолчанию, наиболее широко используемая и оригинальная реализация, написанная преимущественно на языке C.</p>
<p>Иными словами <strong>CPython</strong> &#8212; это программа, которая принимает ваш <strong>код на Python</strong> и выполняет его, преобразуя в понятные машине действия.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/cpython.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2659" src="https://datatalks.ru/wp-content/uploads/2025/12/cpython.jpeg" alt="" width="590" height="307" srcset="https://datatalks.ru/wp-content/uploads/2025/12/cpython.jpeg 590w, https://datatalks.ru/wp-content/uploads/2025/12/cpython-300x156.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/cpython-450x234.jpeg 450w" sizes="(max-width: 590px) 100vw, 590px" /></a></p>
<ol>
<li><strong>Исходный код (Source code)</strong> – mymodule.py преобразуется в <strong>байт-код</strong> с помощью компилятора (compiler) Python</li>
<li><strong>Байт-код (Byte code)</strong> сохраняется в определенном формате (.pyc, .pyo, .pyd) – mymodule.pyc</li>
<li><strong>Виртуальная машина Python (или PVM)</strong> получает байт-код и с помощью интерпретатора преобразует его в бинарный код.</li>
<li>Бинарный или машинный код (Binary code)</li>
<li>Компьютер читает бинарный код и выполняет программу</li>
</ol>
<p><strong>Важно понимать разницу между языком Python и интерпретатором CPython.</strong> Язык Python — это набор правил и синтаксиса (описанных в документации), а CPython конкретная программа, исполняющая код на этом языке.</p>
<hr />
<blockquote><p><strong>Python</strong> &#8212; язык, а <strong>CPython</strong> &#8212; его основной движок.</p></blockquote>
<hr />
<p><strong>Что такое PVM?</strong></p>
<p>Мы знаем, что компьютеры понимают только машинный код, состоящий из нулей и единиц. Поскольку компьютер понимает исключительно машинный код, любой программный код перед выполнением должен быть преобразован в машинный код. Для этого используется компилятор. Обычно компилятор преобразует исходный код программы непосредственно в машинный код.</p>
<p>Компилятор Python выполняет ту же задачу, но несколько иным образом. Он преобразует исходный код программы в другой вид кода, называемый байт-кодом. Каждая инструкция программы на Python преобразуется в набор инструкций байт-кода.</p>
<p><strong>Виртуальная машина Python (Python Virtual Machine, PVM)</strong> принимает этот байт-код и преобразует его в машинный код, чтобы компьютер мог выполнить соответствующие инструкции и вывести итоговый результат. Для выполнения этого преобразования PVM оснащена интерпретатором. Интерпретатор преобразует байт-код в машинный код и передаёт этот машинный код процессору компьютера для выполнения. Поскольку именно интерпретатор играет ключевую роль, виртуальную машину Python часто также называют интерпретатором.</p>
<h3>Альтернативные реализации</h3>
<p>Хотя <strong>CPython</strong> является стандартной реализацией, существуют и другие реализации Python, созданные для конкретных задач, таких как повышение производительности или интеграция с другими платформами:</p>
<ul>
<li><strong>PyPy</strong> — использует компиляцию <strong>Just-In-Time (JIT)</strong>, что позволяет во многих случаях выполнять Python-код значительно быстрее, чем в CPython.</li>
<li><strong>Jython</strong> — написан на <strong>Java</strong> и компилирует Python-код в байткод Java, что позволяет запускать Python на виртуальной машине Java (<strong>JVM</strong>) и взаимодействовать с библиотеками Java.</li>
<li><strong>IronPython</strong> — реализован для <strong>Common Language Infrastructure (CLI)</strong>, благодаря чему может работать на платформе <code>.NET</code>.</li>
<li><strong>MicroPython / CircuitPython</strong> — оптимизированные реализации, предназначенные для микроконтроллеров и встраиваемых систем.</li>
</ul>
<h3>Производительность</h3>
<p>Те, кто имеют опыт работы с компилирующими языками программирования, такими как C и C++, могут заметить несколько отличий в модели выполнения Python.</p>
<ul>
<li><strong>Первое</strong>, что бросается в глаза, – это отсутствие этапа сборки, или вызова утилиты «make»: программный код может запускаться сразу же, как только будет написан.</li>
<li><strong>Второе</strong> отличие: байт код не является двоичным машинным кодом (например, инструкциями для микропроцессора Intel). Байт код – это внутреннее представление программ на языке Python.</li>
</ul>
<p>По этой причине программный код на языке Python не может выполняться так же быстро, как программный код на языке C или C++. <strong>Обход инструкций выполняет виртуальная машина</strong>, а не микропроцессор, и <strong>чтобы выполнить байт код, необходима дополнительная интерпретация</strong>, инструкции которого требуют на выполнение больше времени, чем машинные инструкции микропроцессора. С другой стороны, в отличие от классических интерпретаторов, здесь присутствует дополнительный этап компиляции – интерпретатору не требуется всякий раз снова и снова анализировать инструкции исходного текста. В результате Python способен обеспечить скорость выполнения где то между традиционными компилирующими и традиционными интерпретирующими языками программирования.</p>
<h2>GIL (Global Interpreter Lock)</h2>
<p><strong>GIL (Global Interpreter Lock)</strong> &#8212; интерпретатор Python однопоточный в том смысле, что в каждый момент времени может выполняться только <strong>один участок байт-кода</strong>, даже если в процессе работает несколько потоков. <strong>Глобальная блокировка интерпретатора не позволяет выполнять несколько потоков одновременно.</strong></p>
<p>Python может освободить GIL на время выполнения <strong>операций ввода-вывода (I/O Bound)</strong>, потому что для выполнения ввода-вывода вызывается низкоуровневая функция операционной системы. Эти функции работают за пределами интерпретатора, т. е. никак не могут повредить его внутренние структуры, от чего и призвана защитить GIL.</p>
<p><strong>GIL</strong> был введён для упрощения управления памятью в Python, поскольку многие внутренние операции, такие как создание объектов, по умолчанию не являются потокобезопасными. Без <strong>GIL</strong> нескольким потокам, одновременно обращающимся к общим ресурсам, потребовались бы сложные механизмы блокировок или синхронизации для предотвращения гонок данных и повреждения состояния.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/python_gil.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2647" src="https://datatalks.ru/wp-content/uploads/2025/12/python_gil.jpeg" alt="" width="910" height="348" srcset="https://datatalks.ru/wp-content/uploads/2025/12/python_gil.jpeg 910w, https://datatalks.ru/wp-content/uploads/2025/12/python_gil-300x115.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/python_gil-768x294.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/python_gil-450x172.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/python_gil-780x298.jpeg 780w" sizes="(max-width: 910px) 100vw, 910px" /></a></p>
<p><strong>Когда GIL становится узким местом?</strong></p>
<ul>
<li>В однопоточных программах <strong>GIL</strong> не имеет значения, так как поток обладает эксклюзивным доступом к интерпретатору Python.</li>
<li>В многопоточных <strong>I/O-bound</strong> программах влияние <strong>GIL</strong> менее заметно, поскольку потоки освобождают GIL во время ожидания операций ввода-вывода.</li>
<li>В многопоточных <strong>CPU-bound</strong> задачах <strong>GIL</strong> становится серьёзным узким местом. Несколько потоков, конкурируя за GIL, вынуждены по очереди выполнять байткод Python.</li>
</ul>
<p>Интересный случай, на который стоит обратить внимание, — использование time.sleep. Python фактически рассматривает <code>time.sleep</code> как <strong>I/O-операцию</strong>. Функция <code>time.sleep</code> не является <strong>CPU-bound</strong>, поскольку во время сна не происходит активных вычислений или выполнения байткода Python. Вместо этого ответственность за отслеживание прошедшего времени передаётся операционной системе. В течение этого времени поток освобождает GIL, позволяя другим потокам выполняться и использовать интерпретатор.</p>
<h3><strong>Когда GIL может освобождать поток?</strong></h3>
<table>
<thead>
<tr>
<th>Ситуация</th>
<th>GIL</th>
</tr>
</thead>
<tbody>
<tr>
<td><code inline="">time.sleep()</code></td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> отпущен</td>
</tr>
<tr>
<td>I/O</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> отпущен</td>
</tr>
<tr>
<td><code inline="">lock.acquire()</code> (ожидание)</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> отпущен</td>
</tr>
<tr>
<td>C-расширение без Python API</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> отпущен</td>
</tr>
<tr>
<td>Чистый Python CPU-код</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>Работа с Python-объектами</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td>
</tr>
<tr>
<td>Переключение по таймеру</td>
<td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> временно</td>
</tr>
</tbody>
</table>
<p>Рассмотрим подробно каждую ситуацию.</p>
<p><strong>1 кейс &#8212; блокирующие операции (I/O, sleep, lock wait):</strong> Когда поток заходит в операцию, которая может надолго заблокироваться, CPython отпускает GIL.</p>
<p>time.sleep()</p>
<p><strong>I/O:</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">sock.recv()
sock.send()
open().read()
requests.get()</pre><p>Ожидание примитивов синхронизации</p><pre class="urvanov-syntax-highlighter-plain-tag">lock.acquire()      # если lock уже занят
event.wait()
condition.wait()
queue.get()         # если очередь пуста</pre><p><strong>Кейс 2 &#8212; выполнение C-кода, который отпускает GIL:</strong> Если поток заходит в C-расширение, где внутри есть:</p><pre class="urvanov-syntax-highlighter-plain-tag">Py_BEGIN_ALLOW_THREADS
// тяжёлая работа без Python-объектов
Py_END_ALLOW_THREADS</pre><p>текущий поток временно теряет GIL.</p>
<p>Примеры библиотек:</p>
<ul>
<li>numpy</li>
<li>hashlib</li>
<li>zlib</li>
<li>Pillow</li>
</ul>
<p><strong>C-расширение без Python API:</strong> C-код, который во время выполнения не создаёт, не читает и не изменяет Python-объекты (PyObject*).</p>
<p><strong>Пример Python API в C:</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">PyLong_FromLong(10);      // создаёт Python-объект
PyList_Append(list, x);  // меняет Python-объект
Py_INCREF(obj);          // меняет refcount
PyObject_CallObject(f);  // вызывает Python-функцию
PyErr_SetString(...);    // трогает исключения</pre><p></p>
<h2>CPU-bound vs I/O-bound задачи</h2>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound_cpu_inbound.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2639" src="https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound_cpu_inbound.jpeg" alt="" width="706" height="353" srcset="https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound_cpu_inbound.jpeg 706w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound_cpu_inbound-300x150.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound_cpu_inbound-450x225.jpeg 450w" sizes="(max-width: 706px) 100vw, 706px" /></a></p>
<h3>I/O-bound</h3>
<p><strong>I/O-bound задача</strong> — это задача, выполнение которой блокируется ожиданием операций ввода-вывода (I/O), например сетевых запросов, чтения/записи на диск или работы с внешними устройствами, и поэтому большая часть времени тратится не на вычисления, а на ожидание завершения этих операций.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2642" src="https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound.jpeg" alt="" width="1069" height="329" srcset="https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound.jpeg 1069w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound-300x92.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound-1024x315.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound-768x236.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound-450x138.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/i_o_inbound-780x240.jpeg 780w" sizes="(max-width: 1069px) 100vw, 1069px" /></a></p>
<h3>CPU-bound</h3>
<p><strong>CPU-bound задача</strong> — это задача, выполнение которой ограничено мощностью центрального процессора (CPU), а не ожиданием ввода-вывода. Время её выполнения определяется главным образом количеством вычислительных операций, которые нужно выполнить CPU, а не тем, сколько времени тратится на ожидание данных из внешних источников.</p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2643" src="https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound.jpeg" alt="" width="1068" height="297" srcset="https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound.jpeg 1068w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound-300x83.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound-1024x285.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound-768x214.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound-450x125.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/cpu_inbound-780x217.jpeg 780w" sizes="(max-width: 1068px) 100vw, 1068px" /></a></p>
<h1>Многозадачность в Python</h1>
<h2>Concurrency vs Parallelism</h2>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2654" src="https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python.jpeg" alt="" width="1251" height="504" srcset="https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python.jpeg 1251w, https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python-300x121.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python-1024x413.jpeg 1024w, https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python-768x309.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python-450x181.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/concarency_parallelism_python-780x314.jpeg 780w" sizes="(max-width: 1251px) 100vw, 1251px" /></a></p>
<ul>
<li><strong>Concurrency</strong> — это управление несколькими задачами в одно и то же время, но не обязательно их одновременное выполнение. Задачи могут выполняться по очереди, создавая иллюзию многозадачности.</li>
<li><strong>Parallelism</strong> — это одновременное выполнение нескольких задач, как правило за счёт использования нескольких ядер CPU.</li>
</ul>
<h2>Критерии выбора подхода &#8212; Multithreading, Multiprocessing или Asyncio</h2>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2651" src="https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio.jpeg" alt="" width="861" height="489" srcset="https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio.jpeg 861w, https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio-300x170.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio-768x436.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio-450x256.jpeg 450w, https://datatalks.ru/wp-content/uploads/2025/12/python_threading_multiprocessing_asyncio-780x443.jpeg 780w" sizes="(max-width: 861px) 100vw, 861px" /></a></p>
<p><strong>Multiprocessing (многопроцессность)</strong></p>
<ul>
<li>Лучше всего подходит для CPU-bound задач, требующих интенсивных вычислений.</li>
<li>Используется, когда необходимо обойти GIL — каждый процесс имеет собственный интерпретатор Python, что позволяет достичь настоящего параллелизма.</li>
</ul>
<p><strong>Multithreading (многопоточность)</strong></p>
<ul>
<li>Лучше всего подходит для быстрых I/O-bound задач, так как уменьшается частота переключений контекста, и интерпретатор Python дольше остаётся в одном потоке.</li>
<li>Не подходит для CPU-bound задач из-за ограничений GIL.</li>
</ul>
<p><strong>Asyncio (асинхронность)</strong></p>
<ul>
<li>Идеально подходит для медленных I/O-bound задач, таких как длительные сетевые запросы или обращения к базе данных, поскольку эффективно обрабатывает ожидание и хорошо масштабируется.</li>
<li>Не подходит для CPU-bound задач, если вычисления не выносятся в другие процессы.</li>
</ul>
<h1>threading</h1>
<blockquote><p><span style="color: #ff6600;"><strong>threading</strong></span> в CPython — это инструмент для <span style="color: #ff6600;">I/O</span>-параллелизма.</p></blockquote>
<hr />
<h2>Начальный пример Threading</h2>
<p><strong>Модуль threading</strong> предоставляет способ запуска нескольких потоков (меньших единиц процесса) конкурентно внутри одного процесса. Он позволяет создавать и управлять потоками, делая возможным параллельное выполнение задач с разделяемым адресным пространством памяти. Потоки особенно полезны, когда задачи являются <strong>I/O-bound</strong>, например при работе с файлами или выполнении сетевых запросов, где значительная часть времени тратится на ожидание внешних ресурсов.</p>
<p>Типичный сценарий использования <strong>threading</strong> — управление пулом рабочих потоков, которые могут конкурентно обрабатывать несколько задач. Ниже приведён базовый пример создания и запуска потоков с использованием <strong>Thread</strong>:</p><pre class="urvanov-syntax-highlighter-plain-tag">import threading
import time
import random
from datetime import datetime

def crawl(link):
    print(f"crawl запустился для ссылки {link}. Время вызова: {datetime.now()}")
    time.sleep(random.randint(1, 11))  # Блокирующий I/O (имитация сетевого запроса)
    print(f"crawl завершен для {link}. Время вызова: {datetime.now()}")

links = [
    "https://python.org",
    "https://docs.python.org",
    "https://peps.python.org",
]

# Создаём потоки для каждой ссылки
threads = []
for i, link in enumerate(links):
    # Используем `args` для позиционных аргументов и `kwargs` для именованных
    t = threading.Thread(target=crawl, args=(link,), name=f"Thread-{i+1}")
    threads.append(t)

# Запускаем каждый поток
for t in threads:
    t.start()
    print(f'Поток {t} запущен в {datetime.now()}')

# Ожидаем завершения всех потоков
for t in threads:
    t.join()
    print(f'{t} завершен в {datetime.now()}')</pre><p><strong>Результат:</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">crawl запустился для ссылки https://python.org. Время вызова: 2025-12-27 13:38:45.574122
Поток &lt;Thread(Thread-1, started 138519994431168)&gt; запущен в 2025-12-27 13:38:45.574331
crawl запустился для ссылки https://docs.python.org. Время вызова: 2025-12-27 13:38:45.574500
Поток &lt;Thread(Thread-2, started 138519986038464)&gt; запущен в 2025-12-27 13:38:45.574558
crawl запустился для ссылки https://peps.python.org. Время вызова: 2025-12-27 13:38:45.574701
Поток &lt;Thread(Thread-3, started 138519977645760)&gt; запущен в 2025-12-27 13:38:45.574758
crawl завершен для https://python.org. Время вызова: 2025-12-27 13:38:46.574254
&lt;Thread(Thread-1, stopped 138519994431168)&gt; завершен в 2025-12-27 13:38:46.574417
crawl завершен для https://docs.python.org. Время вызова: 2025-12-27 13:38:47.574610
&lt;Thread(Thread-2, stopped 138519986038464)&gt; завершен в 2025-12-27 13:38:47.574773
crawl завершен для https://peps.python.org. Время вызова: 2025-12-27 13:38:47.574813
&lt;Thread(Thread-3, stopped 138519977645760)&gt; завершен в 2025-12-27 13:38:47.574895</pre><p><strong>Результат второго запуска:</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">crawl запустился для ссылки https://python.org. Время вызова: 2025-12-27 13:44:50.159111
Поток &lt;Thread(Thread-1, started 126693911033536)&gt; запущен в 2025-12-27 13:44:50.159203
crawl запустился для ссылки https://docs.python.org. Время вызова: 2025-12-27 13:44:50.159412
Поток &lt;Thread(Thread-2, started 126693902640832)&gt; запущен в 2025-12-27 13:44:50.159460
crawl запустился для ссылки https://peps.python.org. Время вызова: 2025-12-27 13:44:50.159612
Поток &lt;Thread(Thread-3, started 126693894248128)&gt; запущен в 2025-12-27 13:44:50.159679
crawl завершен для https://docs.python.org. Время вызова: 2025-12-27 13:44:57.159525
crawl завершен для https://peps.python.org. Время вызова: 2025-12-27 13:44:58.159735
crawl завершен для https://python.org. Время вызова: 2025-12-27 13:44:59.159292
&lt;Thread(Thread-1, stopped 126693911033536)&gt; завершен в 2025-12-27 13:44:59.159502
&lt;Thread(Thread-2, stopped 126693902640832)&gt; завершен в 2025-12-27 13:44:59.159545
&lt;Thread(Thread-3, stopped 126693894248128)&gt; завершен в 2025-12-27 13:44:59.159565</pre><p><strong>Общая схема start и join в threading:</strong></p>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/threading_start_join.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2690" src="https://datatalks.ru/wp-content/uploads/2025/12/threading_start_join.jpeg" alt="" width="745" height="626" srcset="https://datatalks.ru/wp-content/uploads/2025/12/threading_start_join.jpeg 745w, https://datatalks.ru/wp-content/uploads/2025/12/threading_start_join-300x252.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/threading_start_join-450x378.jpeg 450w" sizes="(max-width: 745px) 100vw, 745px" /></a></p>
<h3>Деталь реализации CPython</h3>
<p>В CPython из-за <strong>глобальной блокировки интерпретатора (GIL)</strong> только один поток может выполнять Python-код в каждый момент времени (хотя некоторые ориентированные на производительность библиотеки могут обходить это ограничение). Если требуется более эффективно использовать вычислительные ресурсы многоядерных машин, рекомендуется использовать <code>multiprocessing</code> или <code>concurrent.futures.ProcessPoolExecutor</code>. Тем не менее, <strong>threading</strong> остаётся подходящей моделью, если нужно одновременно выполнять несколько <strong>I/O-bound задач</strong>.</p>
<h3>GIL и вопросы производительности</h3>
<p>В отличие от модуля <strong>multiprocessing</strong>, который использует отдельные процессы для обхода GIL, модуль <strong>threading</strong> работает внутри одного процесса, а значит все потоки разделяют одно и то же адресное пространство памяти. Однако GIL ограничивает прирост производительности при работе с CPU-bound задачами, поскольку только один поток может выполнять байткод Python одновременно. Несмотря на это, потоки остаются полезным инструментом для достижения конкурентности во многих сценариях.</p>
<p><strong>Начиная с Python 3.13, существуют <code>free-threaded</code> сборки</strong>, в которых GIL может быть отключён, что позволяет добиться настоящего параллельного выполнения потоков. Однако по умолчанию эта возможность недоступна (см. <strong>PEP 703</strong>).</p>
<h2>Жизненный цикл потока</h2>
<p><a href="https://datatalks.ru/wp-content/uploads/2025/12/thread_lifecycle_python.jpeg"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2679" src="https://datatalks.ru/wp-content/uploads/2025/12/thread_lifecycle_python.jpeg" alt="" width="779" height="379" srcset="https://datatalks.ru/wp-content/uploads/2025/12/thread_lifecycle_python.jpeg 779w, https://datatalks.ru/wp-content/uploads/2025/12/thread_lifecycle_python-300x146.jpeg 300w, https://datatalks.ru/wp-content/uploads/2025/12/thread_lifecycle_python-768x374.jpeg 768w, https://datatalks.ru/wp-content/uploads/2025/12/thread_lifecycle_python-450x219.jpeg 450w" sizes="(max-width: 779px) 100vw, 779px" /></a></p>
<p><strong>Жизненным циклом потоков можно управлять с помощью следующих методов:</strong></p>
<ul>
<li><code>start()</code> &#8212; Дает потоку жизнь.</li>
<li><code>run()</code> &#8212; Этот метод представляет действия, которые должны быть выполнены в<br />
потоке.</li>
<li><code>join([timeout])</code> &#8212; Поток, который вызывает этот метод, приостанавливается, ожидая завершения потока, чей метод вызван. Параметр <code>timeout</code> (число с плавающей точкой) позволяет указать время ожидания (в секундах), по истечении которого приостановленный поток продолжает свою работу независимо от завершения потока, чей метод <code>join</code> был вызван. Вызывать <code>join()</code> некоторого потока можно много раз. Поток не может вызвать метод <code>join()</code> самого себя. Также нельзя ожидать завершения еще не запущенного потока. Слово &#171;join&#187; в переводе с английского означает &#171;присоединить&#187;, то есть, метод, вызвавший <code>join()</code>, желает, чтобы поток по завершении присоединился к вызывающему <strong>метод потоку</strong>.</li>
<li><code>getName() </code>&#8212; Возвращает имя потока. Для главного потока это &#171;<code>MainThread</code>&#171;.</li>
<li><code>setName(name)</code> &#8212; Присваивает потоку имя <strong>name</strong>.</li>
<li><code>isAlive()</code> &#8212; Возвращает истину, если поток работает (метод <code>run()</code> уже вызван, но еще не завершился).</li>
<li><code>isDaemon()</code> &#8212; Возвращает истину, если поток имеет <strong>признак демона</strong>. Программа на Python завершается по завершении всех потоков, не являющихся демонами. Главный поток демоном не является.</li>
<li><code>setDaemon(daemonic)</code> &#8212; Устанавливает признак <strong>daemonic</strong> того, что поток является демоном. Начальное значение этого признака заимствуется у потока, запустившего данный. Признак можно изменять только для потоков, которые еще не запущены.</li>
</ul>
<p><strong>Атрибуты потока:</strong></p>
<ul>
<li><code>t.name</code> &#8212; имя потока</li>
<li><code>t.ident</code> &#8212; Уникальный идентификатор потока (ID) &#8212; None, если поток ещё не запущен</li>
<li><code>threading.current_thread()</code> &#8212; Возвращает объект текущего потока</li>
<li><code>t.daemon = True</code> &#8212; Демон-потоки убиваются при завершении главного потока. Используются для фоновых задач, логирования, heartbeat-потоков. daemon нужно задавать до start()</li>
<li><code>threading.active_count()</code> &#8212; Количество активных потоков</li>
<li><code>threading.enumerate()</code> &#8212; Список всех живых потоков</li>
</ul>
<h2>Реализация потокобезопасной записи результатов с Lock, чтобы избежать race condition</h2>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">import threading
import time
import random
from datetime import datetime

def crawl(link, results, lock):
    print(f"Поток {threading.current_thread().name} запущен. Время вызова: {datetime.now()}")

    # Имитация сетевого запроса
    delay = random.randint(1, 10)
    time.sleep(delay)

    # Имитация полученного JSON
    response = {
        "url": link,
        "status": 200,
        "data": {
            "title": f"Данные с {link}",
            "value": random.randint(1, 100),
        },
        "fetched_at": datetime.now().isoformat(),
        "thread": threading.current_thread().name,
    }

    # Потокобезопасная запись результата
    with lock:
        results[link] = response

    print(f"Поток {threading.current_thread().name} завершен. Запрос длился {delay} секунд. Время завершения: {datetime.now()}")


links = [
    "https://python.org",
    "https://docs.python.org",
    "https://peps.python.org",
]

# Общее хранилище результатов
results = {}

# Lock для синхронизации доступа к results
lock = threading.Lock()

# Создаём потоки
threads = []
for i, link in enumerate(links):
    t = threading.Thread(
        target=crawl,
        args=(link, results, lock),
        name=f"Thread-{i + 1}",
    )
    threads.append(t)

# Запускаем потоки
for t in threads:
    t.start()

# Ждём завершения
for t in threads:
    t.join()

# Итоговый объединённый результат
print("\nИТОГОВЫЙ РЕЗУЛЬТАТ:")
for url, data in results.items():
    print(f"{url} → {data}")</pre><p><strong>Что выполняется в коде:</strong></p>
<ul>
<li><code>t.start()</code> &#8212; Создаёт реальный системный поток. Вызывает <strong>crawl(&#8230;)</strong> в новом потоке. Нельзя вызывать <strong>start()</strong> дважды для одного и того же объекта.</li>
<li><code>t.join()</code> &#8212; Блокирует главный поток и ждёт, пока поток t завершится. Гарантирует, что все данные собраны.</li>
<li><code>threading.current_thread().name</code> &#8212; Позволяет узнать, какой поток сейчас выполняется. Используется для логирования и отладки</li>
</ul>
<table>
<thead>
<tr>
<th>Команда</th>
<th>Где используется</th>
<th>Назначение</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Thread(...)</code></td>
<td>создание потоков</td>
<td>описание задачи</td>
</tr>
<tr>
<td><code>start()</code></td>
<td>запуск</td>
<td>старт выполнения</td>
</tr>
<tr>
<td><code>join()</code></td>
<td>ожидание</td>
<td>синхронизация</td>
</tr>
<tr>
<td><code>current_thread()</code></td>
<td>внутри <code>crawl</code></td>
<td>диагностика</td>
</tr>
<tr>
<td><code>Lock()</code></td>
<td>защита <code>results</code></td>
<td>потокобезопасность</td>
</tr>
</tbody>
</table>
<p><strong>Результат выполнения скрипта:</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">Поток Thread-1 запущен. Время вызова: 2025-12-27 20:45:11.807784
Поток Thread-2 запущен. Время вызова: 2025-12-27 20:45:11.807957
Поток Thread-3 запущен. Время вызова: 2025-12-27 20:45:11.808193
Поток Thread-3 завершен. Запрос длился 1 секунд. Время завершения: 2025-12-27 20:45:12.808385
Поток Thread-1 завершен. Запрос длился 4 секунд. Время завершения: 2025-12-27 20:45:15.808069
Поток Thread-2 завершен. Запрос длился 10 секунд. Время завершения: 2025-12-27 20:45:21.808165

ИТОГОВЫЙ РЕЗУЛЬТАТ:
https://peps.python.org → {'url': 'https://peps.python.org', 'status': 200, 'data': {'title': 'Данные с https://peps.python.org', 'value': 15}, 'fetched_at': '2025-12-27T20:45:12.808319', 'thread': 'Thread-3'}
https://python.org → {'url': 'https://python.org', 'status': 200, 'data': {'title': 'Данные с https://python.org', 'value': 84}, 'fetched_at': '2025-12-27T20:45:15.808019', 'thread': 'Thread-1'}
https://docs.python.org → {'url': 'https://docs.python.org', 'status': 200, 'data': {'title': 'Данные с https://docs.python.org', 'value': 17}, 'fetched_at': '2025-12-27T20:45:21.808113', 'thread': 'Thread-2'}</pre><p></p>
<h2>Основы threading</h2>
<h3>Создание потока через конструктор <code>threading.Thread</code></h3>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">threading.Thread(
    target=None,
    args=(),
    kwargs={},
    name=None,
    daemon=None
)</pre><p>Основные параметры:</p>
<table>
<thead>
<tr>
<th>Параметр</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>target</code></td>
<td>Функция, которая будет выполнена в потоке</td>
</tr>
<tr>
<td><code>args</code></td>
<td>Кортеж позиционных аргументов</td>
</tr>
<tr>
<td><code>kwargs</code></td>
<td>Именованные аргументы</td>
</tr>
<tr>
<td><code>name</code></td>
<td>Имя потока</td>
</tr>
<tr>
<td><code>daemon</code></td>
<td>Демон-поток (<code>True/False</code>)</td>
</tr>
</tbody>
</table>
<h3>Запуск и управление потоками <code>t.start()</code></h3>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">t.start()</pre><p></p>
<ul>
<li>Запускает поток</li>
<li>Внутри вызывает run()</li>
<li>Нельзя вызвать повторно</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">t.run()</pre><p></p>
<ul>
<li>Содержит код потока</li>
<li>Не запускает новый поток, если вызвать напрямую</li>
<li>Обычно не вызывается вручную</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">join(timeout=None)</pre><p></p>
<ul>
<li>Ждёт завершения потока</li>
<li><code>timeout</code> — максимальное время ожидания (в секундах)</li>
</ul>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">t.is_alive()</pre><p></p>
<ul>
<li>Возвращает <code>True</code>, если поток ещё работает</li>
</ul>
<h2>Примитивы синхронизации: Lock, RLock, Semaphore, Event, Condition</h2>
<p><strong>Подробнее:</strong> <a href="https://devpractice.ru/python-lesson-23-concurrency-part-2/" target="_blank" rel="noopener">Python. Урок 23. Потоки и процессы в Python. Часть 2. Синхронизация потоков</a></p>
<hr />
<p>В Python примитивы синхронизации из модуля <code>threading</code> решают одну ключевую задачу: они позволяют нескольким потокам безопасно и предсказуемо взаимодействовать с общим состоянием. Несмотря на наличие <strong>GIL</strong>, эти примитивы остаются необходимыми, потому что <strong>GIL</strong> защищает интерпретатор, но не бизнес-логику и не целостность данных.</p>
<p>Диспетчеры контекста предусмотрены для всех объектов модуля <code>threading</code>, таких как <strong>Lock</strong>, <strong>RLock</strong>, <strong>Condition</strong>, <strong>Semaphore</strong> и <strong>BoundedSemaphore</strong>, то есть для работы с этими объектами может применяться инструкция <code>with</code>.</p>
<p>Начнём с <strong>Lock</strong> и <strong>RLock</strong>, так как они лежат в основе почти всех сценариев синхронизации.</p>
<h3><strong>Lock</strong></h3>
<p><strong>Lock</strong> — это обычный мьютекс, который может быть захвачен только одним потоком в конкретный момент времени. Когда поток вызывает <code>acquire()</code>, он либо сразу получает доступ к критической секции, либо блокируется до тех пор, пока другой поток не освободит <code>lock</code>. После выполнения защищённого участка кода поток обязан вызвать <code>release()</code>. В реальном коде <code>Lock</code> почти всегда используется через контекстный менеджер <code>with</code>, потому что это гарантирует освобождение блокировки даже при исключении. <code>Lock</code> подходит для защиты простых структур данных, таких как <strong>словари</strong>, <strong>списки</strong> или <strong>счётчики</strong>, и <strong>для коротких критических секций</strong>. Важно понимать, что <strong>один и тот же поток не может захватить Lock повторно</strong>: попытка сделать это приведёт к <code>deadlock</code>, когда поток будет ждать самого себя.</p>
<p><strong>Проблема (без Lock)</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">import threading

counter = 0

def increment():
    global counter
    for _ in range(100_000):
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(2)]

for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2757.png" alt="❗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> НЕ гарантировано 200000</pre><p><strong>Решение с Lock</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100_000):
        with lock:          # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> только один поток внутри
            counter += 1

threads = [threading.Thread(target=increment) for _ in range(2)]

for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)  # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> всегда 200000</pre><p><strong>Что гарантирует Lock</strong></p>
<ul>
<li>Только один поток изменяет общее состояние</li>
<li>Нет гонок данных</li>
</ul>
<h3><strong>RLock</strong></h3>
<p>Эта проблема решается с помощью <strong>RLock</strong>, или <strong>reentrant lock</strong>. По сути это мьютекс с учётом владельца. Поток, который уже владеет <code>RLock</code>, может захватить его ещё раз, и <strong>Python</strong> просто увеличит внутренний счётчик захватов. Освобождать такой <code>lock</code> нужно столько же раз, сколько он был захвачен. <code>RLock</code> необходим в более сложных архитектурах, когда функции с защитой <code>lock</code> вызывают друг друга, либо когда публичный метод и внутренний метод используют одну и ту же блокировку. Без <code>RLock</code> такой код почти неизбежно приводит к взаимной блокировке.</p>
<p><strong>Проблема с Lock</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">lock = threading.Lock()

def outer():
    with lock:
        inner()

def inner():
    with lock:   # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> deadlock: поток уже держит lock
        print("inner")</pre><p><strong>Решение с RLock</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">import threading

lock = threading.RLock()

def outer():
    with lock:
        print("outer")
        inner()

def inner():
    with lock:
        print("inner")

threading.Thread(target=outer).start()</pre><p><strong>Что даёт RLock</strong></p>
<ul>
<li>Один поток может захватывать блокировку несколько раз</li>
<li>Важно для рекурсии и вложенных вызовов</li>
</ul>
<h3><strong>Semaphore</strong></h3>
<p>Следующий важный примитив — <strong>Semaphore</strong>. В отличие от <strong>Lock</strong>, который допускает ровно одного владельца, <code>semaphore</code> разрешает одновременно находиться в критической секции ограниченному числу потоков. При создании семафора задаётся счётчик, который уменьшается при <code>acquire()</code> и увеличивается при <code>release()</code>. Пока счётчик положительный, потоки могут входить без ожидания, а когда он становится равным нулю, все последующие вызовы <code>acquire()</code> блокируются. Семантически <strong>семафор</strong> описывает не владение ресурсом, а <strong>количество доступных слотов</strong>. Это делает его удобным для ограничения параллельного доступа к внешним системам, таким как <strong>база данных</strong>, <strong>пул соединений</strong> или <strong>сторонний API</strong>. В отличие от <strong>Lock</strong>, <strong>семафор</strong> не привязан к конкретному потоку, поэтому важно строго соблюдать баланс <code>acquire()</code> и <code>release()</code>, иначе система либо «утечёт» в блокировку, либо начнёт пускать больше потоков, чем предполагалось.</p>
<p>Пример: максимум 2 потока одновременно</p><pre class="urvanov-syntax-highlighter-plain-tag">import threading
import time

semaphore = threading.Semaphore(2)

def worker(name):
    print(f"{name} ждёт доступ")
    with semaphore:
        print(f"{name} вошёл")
        time.sleep(2)
        print(f"{name} вышел")

threads = [
    threading.Thread(target=worker, args=(f"Thread-{i}",))
    for i in range(5)
]

for t in threads:
    t.start()</pre><p><strong>Что гарантирует Semaphore</strong></p>
<ul>
<li>Не более N потоков внутри секции</li>
<li>Остальные ждут освобождения ресурса</li>
</ul>
<h4><strong>BoundedSemaphore</strong></h4>
<p><strong>BoundedSemaphore</strong> — это вариант семафора из модуля <code>threading</code>, который предназначен для строгого контроля количества одновременных доступов к ресурсу и дополнительно защищает от логических ошибок в коде.</p>
<p>По своей сути <strong>BoundedSemaphore</strong> работает так же, как обычный <strong>Semaphore</strong>: он хранит внутренний счётчик, и поток может войти в критическую секцию, только если счётчик больше нуля. При входе счётчик уменьшается, при выходе увеличивается. Это позволяет ограничить количество потоков, которые одновременно используют общий ресурс.</p>
<p>Ключевое отличие <strong>BoundedSemaphore</strong> от <strong>Semaphore</strong> заключается в том, что он не позволяет превысить начальное значение счётчика. Если вызвать <code>release()</code> больше раз, чем было успешных <code>acquire()</code>, <strong>BoundedSemaphore</strong> выбросит исключение <strong>ValueError</strong>. Обычный <strong>Semaphore</strong> такого не делает и молча увеличивает счётчик, что может привести к незаметным ошибкам и нарушению инвариантов программы.</p>
<p>Таким образом, <strong>BoundedSemaphore</strong> полезен в ситуациях, где важно гарантировать, что количество «освобождений» ресурса строго соответствует количеству его захватов, например при реализации пулов соединений или управлении ограниченными системными ресурсами. Он помогает выявлять ошибки проектирования на раннем этапе, вместо того чтобы позволять программе продолжать работу в некорректном состоянии.</p>
<p>Пример использования <strong>BoundedSemaphore</strong> для ограничения числа одновременных работников:</p><pre class="urvanov-syntax-highlighter-plain-tag">import threading
import time

pool = threading.BoundedSemaphore(2)

def worker(name):
    print(f"{name} пытается войти")
    pool.acquire()
    try:
        print(f"{name} работает")
        time.sleep(1)
    finally:
        pool.release()
        print(f"{name} вышел")

threads = [
    threading.Thread(target=worker, args=(f"Thread-{i}",))
    for i in range(4)
]

for t in threads:
    t.start()
for t in threads:
    t.join()</pre><p>Если в этом примере по ошибке вызвать <code>pool.release()</code> дважды в одном потоке, программа сразу упадёт с <code>ValueError</code>, что явно укажет на ошибку управления ресурсом. Именно это поведение и является главным практическим отличием <strong>BoundedSemaphore</strong> от обычного <strong>Semaphore</strong>.</p>
<h3><strong>Event</strong></h3>
<p><strong>Event</strong> решает другую задачу и не предназначен для защиты критических секций. Это <strong>потокобезопасный флаг</strong>, который может быть установлен или сброшен, и который другие потоки могут проверять или ожидать. Внутренне <code>Event</code> хранит состояние <strong>«установлен»</strong> или <strong>«не установлен»</strong>. Когда поток вызывает <code>wait()</code>, он блокируется до тех пор, пока другой поток не вызовет <code>set()</code>. Если событие уже установлено, <code>wait()</code> возвращается сразу. В отличие от <strong>lock</strong>-ов, событие не «потребляется» при ожидании, и все потоки, ожидающие одного и того же события, будут разбужены одновременно. На практике <strong>Event</strong> чаще всего используется для управления жизненным циклом потоков, например для корректной остановки воркеров или для сигнализации о готовности системы к работе. Это более выразительная и безопасная альтернатива общим флагам и бесконечным циклам с <code>sleep</code>.</p>
<p><strong>Пример: ожидание старта</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">import threading
import time

event = threading.Event()

def worker():
    print("Рабочий поток ждёт сигнал...")
    event.wait()          # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/23f3.png" alt="⏳" class="wp-smiley" style="height: 1em; max-height: 1em;" /> блокируется
    print("Рабочий поток получил сигнал!")

def starter():
    time.sleep(3)
    print("Сигнал отправлен!")
    event.set()           # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6a6.png" alt="🚦" class="wp-smiley" style="height: 1em; max-height: 1em;" /> разблокирует всех

threading.Thread(target=worker).start()
threading.Thread(target=starter).start()</pre><p><strong>Event идеально подходит для</strong></p>
<ul>
<li>старт / стоп сигналов</li>
<li><strong>graceful shutdown</strong> &#8212; (плавное или корректное завершение работы) &#8212; это процесс остановки компьютерной системы (приложения, сервера, контейнера), при котором она успевает выполнить необходимые задачи по очистке и сохранению данных перед полным выключением.</li>
<li>ожидания готовности ресурса</li>
</ul>
<h3><strong>Condition</strong></h3>
<p><strong>Condition</strong> является самым сложным и одновременно самым гибким примитивом синхронизации. Он объединяет в себе <strong>мьютекс</strong> и <strong>механизм ожидания уведомлений</strong>. Идея <strong>Condition</strong> заключается в том, что поток может ждать не просто сигнала, а выполнения определённого логического условия, связанного с состоянием программы. Поток захватывает условие, проверяет состояние, и если оно не удовлетворяет требованиям, вызывает <code>wait()</code>. При этом <strong>lock</strong> временно освобождается, чтобы другие потоки могли изменить состояние. Когда другой поток вызывает <code>notify()</code> или <code>notify_all()</code>, ожидающие потоки пробуждаются и снова проверяют условие. Именно повторная проверка условия является ключевым моментом, так как пробуждение не гарантирует, что состояние действительно изменилось нужным образом. <strong>Condition</strong> активно используется в классических <strong>паттернах producer–consumer</strong>, <strong>очередях задач</strong> и системах, где потоки должны реагировать на изменение общего состояния, а не просто на факт события.</p>
<p>Если смотреть на эти примитивы как на систему, то <strong>Lock</strong> и <strong>RLock</strong> отвечают за эксклюзивный доступ, <strong>Semaphore</strong> ограничивает параллелизм, <strong>Event</strong> передаёт сигналы между потоками, а <strong>Condition</strong> позволяет потокам координироваться на основе сложных условий. В продакшене выбор примитива почти всегда диктуется смыслом задачи, а не техническими деталями. Хорошая синхронизация делает код не только корректным, но и читаемым, потому что по выбранному примитиву сразу понятно, как именно потоки должны взаимодействовать друг с другом.</p>
<p><strong>Producer / Consumer с Condition</strong></p><pre class="urvanov-syntax-highlighter-plain-tag">import threading
import time
import random

condition = threading.Condition()
queue = []
MAX_ITEMS = 5

def producer():
    for i in range(10):
        time.sleep(random.uniform(0.1, 0.5))
        with condition:
            while len(queue) &gt;= MAX_ITEMS:
                condition.wait()   # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/23f3.png" alt="⏳" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ждём, пока потребитель заберёт
            queue.append(i)
            print(f"Producer добавил {i}")
            condition.notify()     # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f514.png" alt="🔔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> сигнал потребителю

def consumer():
    for _ in range(10):
        with condition:
            while not queue:
                condition.wait()   # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/23f3.png" alt="⏳" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ждём данные
            item = queue.pop(0)
            print(f"Consumer забрал {item}")
            condition.notify()     # <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f514.png" alt="🔔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> сигнал производителю
        time.sleep(random.uniform(0.2, 0.6))

threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()</pre><p><strong>Condition = Lock + Event</strong></p>
<ul>
<li><code>wait()</code> → отпускает lock и ждёт</li>
<li><code>notify()</code> → будит ожидающий поток</li>
<li>Позволяет ждать логических условий, а не просто блокировки</li>
</ul>
<h3><strong>Barrier</strong></h3>
<p><code>threading.Barrier</code> — это примитив синхронизации в Python, который позволяет группе потоков одновременно ожидать друг друга в определенной точке выполнения (контрольной точке) перед тем, как продолжить работу.</p>
<p>todo</p>
<h3><strong>Итоговая таблица</strong></h3>
<table>
<thead>
<tr>
<th>Примитив</th>
<th>Для чего</th>
</tr>
</thead>
<tbody>
<tr>
<td><code inline="">Lock</code></td>
<td>Простая защита общего состояния</td>
</tr>
<tr>
<td><code inline="">RLock</code></td>
<td>Вложенные / рекурсивные блокировки</td>
</tr>
<tr>
<td><code inline="">Semaphore</code></td>
<td>Ограничение количества потоков</td>
</tr>
<tr>
<td><code inline="">Event</code></td>
<td>Сигналы между потоками</td>
</tr>
<tr>
<td><code inline="">Condition</code></td>
<td>Сложная координация и ожидание условий</td>
</tr>
</tbody>
</table>
<h1>concurrent.futures</h1>
<p>todo</p>
<h1>multiprocessing</h1>
<p>Теория<br />
Разница между:<br />
fork / spawn / forkserver</p>
<p>IPC (межпроцессное взаимодействие):<br />
Queue<br />
Pipe<br />
Manager<br />
Стоимость сериализации (pickle)<br />
Copy-on-write (Linux)</p>
<p>Практика<br />
Параллельная обработка данных<br />
Использование multiprocessing.Pool</p>
<p>Бенчмарк:<br />
threading vs multiprocessing<br />
Поймать баг с pickling’ом</p>
<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cc.png" alt="📌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Продакшн insight: multiprocessing часто убивает latency, если использовать бездумно.</p>
<h1>asyncio</h1>
<p>Ключевая тема для highload backend.</p>
<p>Теория<br />
Event loop<br />
Coroutine<br />
Awaitable<br />
Task vs Future<br />
Cooperative multitasking<br />
Почему async ≠ threading</p>
<p>Практика<br />
Переписать синхронный код в async<br />
Одновременные HTTP-запросы (aiohttp)<br />
Ограничение параллелизма (Semaphore)</p>
<p>Ошибки:<br />
blocking call внутри async<br />
забытый await</p>
<p>Критически важно: понимание, почему один blocking вызов убивает весь сервис.</p>
<p>Сообщение <a href="https://datatalks.ru/python-threading-multiprocessing-asyncio/">Python &#8212; Многозадачность, конкурентность и асинхронность</a> появились сначала на <a href="https://datatalks.ru">DataTalks.RU. Data Engineering / DWH / Data Pipeline</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://datatalks.ru/python-threading-multiprocessing-asyncio/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
