Рус Eng Cn Translate this page:
Please select your language to translate the article


You can just close the window to don't translate
Library
Your profile

Back to contents

Software systems and computational methods
Reference:

Practical use of asynchronous programming in Python with Asyncio

Savostin Petr Alekseevich

Bachelor's Degree, Department of Algorithmic Languages, Lomonosov Moscow State University 

117437, Russia, g. Moscow, ul. Leninskie Gory, 1, str. 52

petersavostin@gmail.com
Efremova Natalya Ernestovna

PhD in Physics and Mathematics

Assistant, Department of Algorithmic Languages, Lomonosov Moscow State University

119234, Russia, Moskovskaya oblast', g. Moscow, ul. Leninskie Gory, 1, str. 52, aud. 707

nvasil@list.ru

DOI:

10.7256/2454-0714.2018.2.25851

Received:

27-03-2018


Published:

13-06-2018


Abstract: The subject of the study is the study of the basic principles of asynchronous programming with the Asyncio package and their application for solving applied problems in Python. Since the Python interpreter uses the Global Interpreter Lock synchronization method, which limits the ability to parallelize programs in the given language and, as a result, does not allow achieving the greatest efficiency, the use of asynchronous programming technologies allows to significantly increase the speed of programs in this language, avoiding the mentioned limitations. The above described approach to creating programs is used in many tasks, for example: when creating a web server, client-server application, when extracting data from a resource by a web crawler. This paper is devoted to explaining the basic principles of working with the packageAsyncio in Python. Since the Russian-language literature on this package is often not enough to understand the basics of asynchronous programming in Python, this article gives examples of the use of this technology with explanations.


Keywords:

python programming language, asyncio library, asynchronous programming, web-crawling, parsing, couroutines, scraping, GIL, parallel computing, data extraction

This article written in Russian. You can find original text of the article here .

Практическое применение асинхронного программирования на языке Python при помощи пакета Asyncio

Введение

Сейчас высокоуровневый язык программирования Python [3] является одним из самых востребованных языков в сфере информационных технологий. На нем реализованы многие проекты, связанные с обработкой текстов на естественном языке, анализом больших данных, машинным обучением и пр. [1]. Основное преимущество данного языка заключается в том, что он достаточно прост в использовании. Помимо этого, для языка Python доступно множество библиотек и пакетов [2], которые существенно облегчают и ускоряют разработку программ.

Язык программирования Python стремительно развивается, и с выходом новых версий порой невозможно найти литературу, описывающую новые возможности языка. Например, в Python версии 3.4 появился пакет asyncio [2], предназначенный для организации асинхронного программирования с использованием цикла событий сопрограмм; подробное описание принципов работы с данным пакетом на русском языке пока отсутствует.

Асинхронное программирование в Python

В интерпретаторе Python используется Global Interpreter Lock (GIL), который накладывет ограничение на потоки, а именно запрещает использование несколько процессоров одновременно одной программой [5]. GIL – это способ синхронизации потоков, одновременно обращающихся к одному и тому же участку памяти: когда один процесс захватывает его, GIL блокирует остальные процессы [6]. Поэтому для ускорения работы последовательной программы вместо параллельного приходится использовать асинхронное программирование.

Концепция асинхронного программирования подразумевает явное установление особых точек при выполнении вычислений (процессов). Эти точки позволяют отдельным вычислениям избегать ожидания завершения всех остальных вычислений, как это происходит в случае последовательного программирования [4].

Основное отличие асинхронной программы от последовательной заключается в том, что при асинхронных вычислениях порядок выполнения операций может отличаться от порядка их следования в программе [4].

Ранее, для организации асинхронного программирования в языке Python использовались пакеты gevent.

Асинхронное программирование с использованием библиотеки asyncio основано на следующих понятиях:

· Цикл событий (event loop) – планировщик задач, который управляет их выполнением.

· Сопрограмма (coroutine) – компонент программы, решающий задачу, который поддерживает множество входных точек (а не одну, как подпрограмма), остановку и продолжение выполнения с сохранением определённого положения [2]. Сопрограмма запускается только в цикле событий и должна содержать в себе ключевое слово await, после которого управление из сопрограммы передается обратно в цикл событий.

· Фьючер (future) – объект, который хранит в себе состояние выполнения задачи:

· ожидание (pending);

· выполнение (running);

· завершение (done);

· исключение (exception).

Приведем простой пример вызова функции, которая выводит сообщение последовательно и пример асинхронного вызова с помощью цикла событий:

Последовательный код

Асинхронный код

# Функция ждет секунду и печатает сообщение

def hello_world():

sleep(1.0)

print("Hello World!")

hello_world()

import asyncio

# Функция ждет секунду и печатает сообщение

async def hello_world():

await asyncio.sleep(1.0)

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).

Последовательный код

Асинхронный код

Импортирование необходимых пакетов.

Urllib для создания запросов на сервер

Bs4 для парсинга страниц

Импортирование необходимых пакетов.

Asyncio для асинхронного программирования

Aiohttp для асинхронных запросов

Bs4 для парсинга страниц

import urllib.request

from bs4 import BeautifulSoup

from urllib.parse import urljoin

import asyncio

import aiohttp

from bs4 import BeautifulSoup

from urllib.parse import urljoin

start_url используется для задание первой ссылки, с которой начнется обход

depth глубина обхода

url_list хранит в себе список адресов для обхода

start_url = 'http://www.aclweb.org/anthology/'

depth = 2

url_list = [start_url]

start_url = 'http://www.aclweb.org/anthology/'

depth = 1

url_list = [start_url]

parse_for_links функция, которая ищет ссылки в HTML коде страницы

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]

async def fetch_async(url):

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:

url_list += 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 является удобным в использовании: он позволяет с минимальным количеством затрат при переписывании последовательной версии программы в асинхронную, получить более эффективную программу.

Показано, что применение библиотеки позволяет ускорить работу программы за счет разбиения решаемой задачи на несколько подзадач, которые могут выполняться асинхронно.

References
1.
2.
3.
4.
5.
6.