Практическое применение асинхронного программирования на языке Python при помощи пакета Asyncio
Савостин Петр Алексеевич
бакалавр, кафедра Алгоритмических Языков, Московский Государственний Университет им.
Ломоносова
117437, Россия, г. Москва, ул. Ленинские Горы, 1, стр. 52 И [email protected]
Ефремова Наталья Эрнестовна
кандидат физико-математмческих наук
ассистент, кафедра Алгоритмических языков, Московский Государственный Университет имени М.В.
Ломоносова
119234, Россия, Московская область, г. Москва, ул. Ленинские Горы, 1, стр. 52, ауд. 707
Статья из рубрики "Языки программирования"
Аннотация.
Предметом исследования является изучение основных принципов асинхронного программирования с помощью пакета Asyncio и их применение для решения прикладных задач на языке Python. Поскольку в интерпретаторе языка Python используется способ синхронизации потоков Global Interpreter Lock, ограничивающий возможность распараллеливания программ на данном языке и, как следствие, не позволяет достичь наибольшей эффективности, использование технологий асинхронного программирования позволяет значительно увеличить скорость работы программ на данном языке, обходя упомянутые ограничения. Вышеописанный подход к созданию программ применяется в решении многих задач, например: при создании веб-сервера, клиент-серверного приложения, при извлечении данных с информационного ресурса веб-краулером. Данная работа посвящена объяснению основных принципов работы с пакетомAsyncio на языке Python. Поскольку русскоязычной литературы по данному пакету зачастую не хватает для того, чтобы понять основы асинхронного программирования в языке Python, в этой статье приводятся примеры использования данной технологии с пояснениями.
Ключевые слова: язык программирования python, библиотека asyncio, асинхронное программирование, веб-краулинг, парсинг, сопрограммы, скрапинг, GIL, параллельные вычисления, извлечение данных
DOI:
10.7256/2454-0714.2018.2.25851
Дата направления в редакцию:
27-03-2018
Дата рецензирования:
04-04-2018
Практическое применение асинхронного программирования на языке Python при помощи пакета Asyncio
Введение
Сейчас высокоуровневый язык программирования Python I-31 является одним из самых востребованных языков в сфере информационных технологий. На нем реализованы многие проекты, связанные с обработкой текстов на естественном языке, анализом
больших данных, машинным обучением и пр. -Ш. Основное преимущество данного языка заключается в том, что он достаточно прост в использовании. Помимо этого, для языка
Python доступно множество библиотек и пакетов Í21, которые существенно облегчают и ускоряют разработку программ.
Язык программирования Python стремительно развивается, и с выходом новых версий порой невозможно найти литературу, описывающую новые возможности языка. Например, в Python версии 3.4 появился пакет asyncio I-21, предназначенный для организации асинхронного программирования с использованием цикла событий сопрограмм; подробное описание принципов работы с данным пакетом на русском языке пока отсутствует.
Асинхронное программирование в Python
В интерпретаторе Python используется Global Interpreter Lock (GIL), который накладывет ограничение на потоки, а именно запрещает использование несколько процессоров
одновременно одной программой í51. GIL - это способ синхронизации потоков, одновременно обращающихся к одному и тому же участку памяти: когда один процесс
захватывает его, GIL блокирует остальные процессы Поэтому для ускорения работы последовательной программы вместо параллельного приходится использовать асинхронное программирование.
Концепция асинхронного программирования подразумевает явное установление особых точек при выполнении вычислений (процессов). Эти точки позволяют отдельным вычислениям избегать ожидания завершения всех остальных вычислений, как это происходит в случае последовательного программирования
Основное отличие асинхронной программы от последовательной заключается в том, что при асинхронных вычислениях порядок выполнения операций может отличаться от порядка их следования в программе [41.
Ранее, для организации асинхронного программирования в языке Python использовались пакеты gevent.
Асинхронное программирование с использованием библиотеки asyncio основано на следующих понятиях:
■ Цикл событий (event loop ) - планировщик задач, который управляет их выполнением.
■ Сопрограмма (coroutine ) - компонент программы, решающий задачу, который поддерживает множество входных точек (а не одну, как подпрограмма), остановку и
продолжение выполнения с сохранением определённого положения i21. Сопрограмма запускается только в цикле событий и должна содержать в себе ключевое слово await , после которого управление из сопрограммы передается обратно в цикл событий.
■ Фьючер (future ) - объект, который хранит в себе состояние выполнения задачи:
■ ожидание (pending );
■ выполнение (running );
■ завершение (done );
■ исключение (exception ).
Приведем простой пример вызова функции, которая выводит сообщение последовательно и пример асинхронного вызова с помощью цикла событий:
Последовательный код Асинхронный код
# Функция ждет секунду и import asyncio
печатает сообщение
# Функция ждет секунду и печатает
def hello_world(): сообщение
sleep(1.0) async def hello_world():
print("Hello World!") await asyncio.sleep(1.0)
hello_world() print("Hello World!")
loop = asyncio.get_event_loop()
loop.run_until_complete(hello_world())
loop.close()
Между двумя программами есть некоторые различия. При объявлении функции, которые должны будут вызываться асинхронно используется ключевое слово async, которое. Ключевое слово await обозначает, что в этой точке сопрограмма передает управление циклу событий, при этом начинает выполнять команду, стоящую за ней. Переменная loop представляет собой объект цикла событий (планировщика), чтобы выполнить функцию hello_world() асинхронно, нужно воспользоваться функцией run_until_complete. В конце программы цикл событий завершается командой close().
Отметим, что основным удобством использования библиотеки asyncio является то, что код, написанный с ее помощью, почти не отличается от последовательного кода.
Использование asyncio при извлечении данных с web-страниц
Библиотека asyncio была использована нами при создании программы, осуществляющей:
■ поиск существующих URL - единых указателей ресурса (обычно такая программа называется краулером );
■ извлечение данных с найденных html-страниц.
При написании краулера необходимо было организовать
Покажем, каким образом был организован обход html-страниц с помощью библиотеки asyncio. Для этого опишем алгоритм работы простейшего краулера и приведем фрагмент программы, реализующей его.
Алгоритм работы краулера заключается в следующем:
1. Вводится стартовый URL страницы.
2. Программа посылает запрос на сервер по данному URL.
3. Программа получает ответ от сервера в виде html-страницы.
4. Из полученной страницы извлекаются ссылки на другие страницы.
5. Переходим на шаг 2.
Процесс заканчивается, когда достигается заданная глубина обхода.
Ниже приведен код для последовательной и асинхронной версии реализации краулера. На вход программам подается стартовый URL и глубина обхода (depth), на выходе получаем список обойденных URL (url_list).
Последовательный код Асинхронный код
Импортирование необходимых Импортирование необходимых пакетов.
пакетов. Asyncio для асинхронного программирования
Urllib для создания запросов на сервер Aiohttp для асинхронных запросов
Bs4 для парсинга страниц B s 4 для па рс инг а стра ниц
import urllib.request import asyncio
from bs4 import BeautifulSoup import aiohttp
from urllib.parse import urljoin from bs4 import BeautifulSoup from urllib.parse import urljoin
start_url используется для
задание первой ссылки, с
которой начнется обход
depth глубина обхода
url_list хранит в себе список
адресов для обхода
sta rt_url = start_url = 'http://www.aclweb.org/anthology/'
'http://www.aclweb.org/anthology/' depth = 1
depth = 2 url_list = [start_url]
u rl_l ist = [start_url]
parse_for_links функция, которая ищет ссылки в HTML коде страницы
def parse for links(url text)- asvnr def fetch asvnr(urlV 14
soup = BeautifulSoup(text, "html.parser")
tags = soup.findAll('a', href=True)
return [urljoin(url, tag['href']) for tag in tags]
def parse_for_links(url, text):
soup = BeautifulSoup(text, "html.parser")
tags = soup.findAll('a', href=True)
return [urljoin(url, tag['href']) for tag in tags]
get_links функция, которая делает запрос по данному URL и ищет ссылки в коде страницы
def get_links(url): try:
req = urllib.request.Request(url, headers = {'User-Agent': 'Mozilla/5.0'})
req = urllib.request.urlopen(req)
req = map(lambda x: x.decode('utf-8'), req)
return parse_for_links(url,
''.join(list(req)))
except:
return []
try:
async with aiohttp.ClientSession() as session: async with session.get(url, timeout=15) as resp: assert resp.status == 200 global url_list
url_list += parse_for_links(url, await resp.text()) except:
print('Error while downloading: ', url) async def wrap_tasks(urls): tasks = [] for url in urls:
tasks.append(asyncio.ensure_future(fetch_async(url))) await asyncio.wait(tasks)
for i in range(depth): print('Depth: ', depth) for url in url_list: u rl_l ist += get_links(url) print(url_list)
for i in range(depth): print('Depth: ', depth)
ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(wrap_tasks(url_list))
print(url_list)
Как видно есть несколько отличий между последовательным и асинхронным кодом:
■ сопрограмма fetch_async посылает асинхронный запрос на сервер, в то время, как запрос обрабатывается, передает управление циклу событий и ждет ответа от сервера.
■ wrap_tasks - создает список фьючерсов из сопрограмм, которые будут выполнятся в цикле событий
Заключение
Мы рассмотрели примеры создания программ с использованием технологий асинхронного программирования на языке программирования Python . Как видно из приведенных программ, пакет asyncio является удобным в использовании: он позволяет
j
с минимальным количеством затрат при переписывании последовательной версии программы в асинхронную, получить более эффективную программу.
Показано, что применение библиотеки позволяет ускорить работу программы за счет разбиения решаемой задачи на несколько подзадач, которые могут выполняться асинхронно.
Библиография
1. Applications written in Python [Электронный ресурс]. - Электрон. дан. - URL-https-//wiki.python.org/moin/Applirations (дата обращения- 25.03.2018).
2. Asyncio documentation [Электронный ресурс]. - Электрон. дан. - URL-http-//asyncio.readthedocs.io (дата обращения- 25.03.2018).
3. Python [Электронный ресурс]. - Электрон. дан. - URL- http://www.python.org/ (дата обращения- 08.02.2018).
4. Журнал Tproger [Электронный ресурс]. - Электрон. дан. - URL-
https -//tproger.ru/translations/programming-concepts-concurrency/ (дата обращения -25.03.2018)
5. Дональд Кнут. Искусство программирования, том 1. Основные алгоритмы = The Art of Computer Programming, vol.1. Fundamental Algorithms. - 3-е изд. - М.- «Вильямс», 2006. - С. 720. - ISBN 0-201-89683-4.Раздел 1.4.2- Сопрограммы, стр. 229-236.
6. Саммерфилд М., Программирование на Python 3. Подробное руководство, 2009.