
- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода CPython
- •Что в исходном коде?
- •Настройка среды разработки
- •IDE или редактор?
- •Настройка Visual Studio
- •Настройка Visual Studio Code
- •Настройка Vim
- •Выводы
- •Компиляция CPython
- •Компиляция CPython на macOS
- •Компиляция CPython на Linux
- •Установка специализированной версии
- •Знакомство с Make
- •Make-цели CPython
- •Компиляция CPython на Windows
- •Профильная оптимизация
- •Выводы
- •Грамматика и язык Python
- •Спецификация языка Python
- •Генератор парсеров
- •Повторное генерирование грамматики
- •Выводы
- •Конфигурация и ввод
- •Конфигурация состояния
- •Структура данных конфигурации среды выполнения
- •Конфигурация сборки
- •Сборка модуля из входных данных
- •Выводы
- •Генерирование конкретного синтаксического дерева
- •Парсер/токенизатор CPython
- •Абстрактные синтаксические деревья
- •Важные термины
- •Пример: добавление оператора «почти равно»
- •Выводы
- •Компилятор
- •Исходные файлы
- •Важные термины
- •Создание экземпляра компилятора
- •Флаги будущей функциональности и флаги компилятора
- •Таблицы символических имен
- •Основная компиляция
- •Ассемблер
- •Создание объекта кода
- •Использование Instaviz для вывода объекта кода
- •Пример: реализация оператора «почти равно»
- •Выводы
- •Цикл вычисления
- •Исходные файлы
- •Важные термины
- •Построение состояния потока
- •Построение объектов кадров
- •Выполнение кадра
- •Стек значений
- •Пример: добавление элемента в список
- •Выводы
- •Управление памятью
- •Выделение памяти в C
- •Проектирование системы управления памятью Python
- •Аллокаторы памяти CPython
- •Область выделения объектной памяти и PyMem
- •Область выделения сырой памяти
- •Нестандартные области выделения памяти
- •Санитайзеры выделенной памяти
- •Арена памяти PyArena
- •Подсчет ссылок
- •Сборка мусора
- •Выводы
- •Параллелизм и конкурентность
- •Модели параллелизма и конкурентности
- •Структура процесса
- •Многопроцессорный параллелизм
- •Многопоточность
- •Асинхронное программирование
- •Генераторы
- •Сопрограммы
- •Асинхронные генераторы
- •Субинтерпретаторы
- •Выводы
- •Объекты и типы
- •Примеры этой главы
- •Встроенные типы
- •Типы объектов
- •Тип type
- •Типы bool и long
- •Тип строки Юникода
- •Словари
- •Выводы
- •Стандартная библиотека
- •Модули Python
- •Модули Python и C
- •Набор тестов
- •Запуск набора тестов в Windows
- •Запуск набора тестов в Linux или macOS
- •Флаги тестирования
- •Запуск конкретных тестов
- •Модули тестирования
- •Вспомогательные средства тестирования
- •Выводы
- •Отладка
- •Обработчик сбоев
- •Компиляция поддержки отладки
- •LLDB для macOS
- •Отладчик Visual Studio
- •Отладчик CLion
- •Выводы
- •Бенчмаркинг, профилирование и трассировка
- •Использование timeit для микробенчмарка
- •Использование набора тестов производительности Python
- •Профилирование кода Python с использованием cProfile
- •Выводы
- •Что дальше?
- •Создание расширений C для CPython
- •Улучшение приложений Python
- •Участие в проекте CPython
- •Дальнейшее обучение
- •Препроцессор C
- •Базовый синтаксис C
- •Выводы
- •Благодарности

254 Параллелизм и конкурентность
start = time.time()
host = "localhost" # Замените вашим хостом results = asyncio.run(scan(80, 100, host)) for result in results:
print("Port {0} is open".format(result))
print("Completed scan in {0} seconds".format(time.time() - start))
Сканирование занимает чуть более секунды:
$ python portscanner_async.py Port 80 is open
Completed scan in 1.0058400630950928 seconds
АСИНХРОННЫЕ ГЕНЕРАТОРЫ
Концепции генераторов и сопрограмм, с которыми вы познакомились к настоящему моменту, можно объединить в асинхронные генераторы.
Если функция объявляется с ключевым словом async и содержит оператор yield, при вызове она преобразуется в объект асинхронного генератора.
Асинхронные генераторы, как и обычные, должны выполняться конструкцией, поддерживающей протокол. Вместо __next__() асинхронные генераторы содержат метод __anext__().
Обычный цикл for не поймет асинхронный генератор, поэтому вместо него необходимо использовать команду async for.
Функцию check_port() можно преобразовать в асинхронный генератор, который через yield возвращает следующий открытый порт, пока не достигнет последнего порта или не найдет заданное количество открытых портов:
async def check_ports(host: str, start: int, end: int, max=10): found = 0
for port in range(start, end): try:
future = asyncio.open_connection(host=host, port=port) r, w = await asyncio.wait_for(future, timeout=timeout) yield port
found += 1 w.close()
if found >= max: return
except asyncio.TimeoutError: pass # Закрыт
Книги для программистов: https://t.me/booksforits

Субинтерпретаторы 255
Для выполнения кода используется команда async for:
async def scan(start, end, host): results = []
async for port in check_ports(host, start, end, max=1): results.append(port)
return results
Полный код примера находится в файле cpython-book-samples 33 portscanner_ async_generators.py.
СУБИНТЕРПРЕТАТОРЫ
К настоящему моменту мы рассмотрели:
zz Параллельное выполнение с многопроцессной обработкой. zz Конкурентное выполнение с потоками и async.
У многопроцессной обработки есть недостатки: межпроцессные коммуникации, использующие каналы и очереди, работают медленнее общей памяти, а дополнительные затраты на запуск нового процесса весьма значительны.
Дополнительные затраты у многопоточных решений и async невелики, но они не обеспечивают полноценного параллельного выполнения из-за гарантий потоковой безопасности в GIL.
Еще один вариант — создание субинтерпретаторов (subinterpreters); он имеет более низкие дополнительные затраты, чем у многопроцессных решений, и позволяет иметь отдельную блокировку GIL для каждого субинтерпретатора.
Вконце концов, GIL — глобальная блокировка интерпретатора.
Всреде выполнения CPython всегда существует только один интерпретатор. Он содержит информацию о состоянии, а внутри интерпретатора может быть один или несколько потоков Python. Интерпретатор является контейнером для цикла вычисления. Он также управляет своей памятью, обеспечивает подсчет ссылок и сборку мусора.
ВCPython поддерживаются низкоуровневые C API для создания интерпретаторов, например Py_NewInterpreter():
Книги для программистов: https://t.me/booksforits

256 Параллелизм и конкурентность
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
• • |
|
|
|
|
|
|
|
|
|
|
|
|
|
• |
|
|
|
|
• |
|
|
|
0 |
|
|
• |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
• |
|
|
||
|
( ) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
• |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GIL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0 |
|
|
1 ... n |
|
|
|
|
|
|
|
||
|
( ) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
• • |
|
|
|
|
|
|
|
|
|
|
|
|
|
• |
|
|
|
|
• |
|
|
|
|
|
|
• |
|
|
|
|
|
|
|
|
|
|
1 |
|
|
|
|
|
|
|
|
• |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
• |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GIL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0 |
|
|
1 ... n |
|
|
|
|
|
|
|
||
|
( ) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Книги для программистов: https://t.me/booksforits

Субинтерпретаторы 257
ПРИМЕЧАНИЕ
Модуль subinterpreters остается экспериментальным вверсии 3.9,так что API еще может изменяться, а реализация содержит ошибки.
Так как состояние интерпретатора содержит арену выделения памяти — коллекцию всех указателей на объекты Python (локальные и глобальные), — субинтерпретаторы не могут обращаться к глобальным переменным других интерпретаторов.
Как и в случае с многопроцессной обработкой, для совместного использования объектов интерпретаторами необходимо сериализовать их или использовать ctype и некоторую разновидность IPC1 (сеть, диск, общая память).
Исходные файлы
Ниже перечислены исходные файлы, относящиеся к субинтерпретаторам.
ФАЙЛ НАЗНАЧЕНИЕ
Lib _xxsubinterpreters.c Реализация модуля subinterpreters на C
Python pylifecycle.c |
Реализация API управления интерпретатором на C |
Пример
В последнем примере код подключения будет храниться в строке. В версии 3.9 субинтерпретаторы могут выполняться только кодом в строке (string).
Для запуска каждого субинтерпретатора запускается список потоков с обратным вызовом функции run().
Эта функция:
zz создает канал взаимодействия;
zz запускает новый субинтерпретатор;
zz отправляет субинтерпретатору выполняемый код; zz получает данные по каналу взаимодействия;
1 Inter Process Communications; межпроцессное взаимодействие. — Примеч. ред.
Книги для программистов: https://t.me/booksforits

258 Параллелизм и конкурентность
zz если подключение к порту происходит успешно, он добавляется в потокобезопасную очередь.
cpython-book-samples 33 portscanner_subinterpreters.py
import time
import _xxsubinterpreters as subinterpreters from threading import Thread
import textwrap as tw from queue import Queue
timeout = 1 # В секундах
def run(host: str, port: int, results: Queue):
# Создание канала взаимодействия
channel_id = subinterpreters.channel_create() interpid = subinterpreters.create() subinterpreters.run_string(
interpid,
tw.dedent(
"""
import socket; import _xxsubinterpreters as subinterpreters sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout)
result = sock.connect_ex((host, port)) subinterpreters.channel_send(channel_id, result) sock.close()
"""),
shared=dict( channel_id=channel_id, host=host,
port=port,
timeout=timeout
))
output = subinterpreters.channel_recv(channel_id) subinterpreters.channel_release(channel_id)
if output == 0: results.put(port)
if __name__ == '__main__': start = time.time()
host = "127.0.0.1" # Выберите ваш хост threads = []
results = Queue()
for port in range(80, 100):
t = Thread(target=run, args=(host, port, results)) t.start()
threads.append(t) for t in threads:
Книги для программистов: https://t.me/booksforits