предисловие
Многие люди уже знают о сопрограммах Python и о том, как их использовать.Среди многих сетевых моделей есть много решений для достижения высокого параллелизма, таких как многопоточность, многопроцессорность и сопрограммы. В методе сопрограммы планирование исходит от пользователя, и пользователь может получить состояние в функции, чтобы реализовать неблокирующее использование программы. Использование сопрограмм позволяет эффективно выполнять параллельные задачи. Появился после Python 3.5async/awaitВ этой статье будет подробно описано использование async/await и метод реализации неблокирующего сервера с помощью Tornado.
Статус-кво сопрограмм Python и вызовов ввода-вывода
Распространенное использование сопрограмм
import asyncio
async def do_some_work(x):
print('Waiting: ', x)
# 协程遇到await,事件循环将会挂起该协程,执行别的协程,直到其他的协程也挂起或者执行完毕,再进行下一个协程的执行。
# 此处先挂起,再执行await的协程,最后执行return
await asyncio.sleep(x)
return 'Done after {}s'.format(x)
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
В приведенном выше примере реализована сопрограмма.Если есть несколько сопрограмм, выполняющих функцию do_some_work одновременно, неблокирующий эффект может быть достигнут без блокировки и ожидания завершения каждого сна.
Однако при общей разработке Python мы столкнемся со многими вызовами ввода-вывода, такими как запросы сетевого ввода-вывода, запросы ввода-вывода базы данных и т. д. Эти вызовы выполняются долго. требуется операция, то в модели одного процесса Python ввод-вывод будет блокироваться в течение длительного времени, что приводит к длительному времени отклика или даже тайм-ауту для последующих запросов, поэтому нам нужно использовать неблокирующие методы для более трудоемких Операции ввода-вывода для достижения значительного повышения эффективности выполнения и производительности системы.
Общий метод вызова ввода-вывода в Python
В общем, когда мы реализуем вызовы ввода-вывода в Python, такие как HTTP-запросы, мы будем использовать библиотеку запросов или библиотеку urllib, которая поставляется с ней.Методы следующие:
from urllib import parse,request
import json
# POST请求 当request中包含data参数的时候,是POST请求,反之是GET请求
textmod = {"username": "admin", "password": "123456"}
textmod = json.dumps(textmod).encode(encoding='utf-8')
header_dict = {'Accept': 'application/json', 'Content-Type': 'application/json'}
url = 'http://localhost:8080/api/xxx'
req = request.Request(url=url, data=textmod, headers=header_dict)
res = request.urlopen(req)
res = res.read()
Вышеупомянутый общий метод вызова ввода-вывода является блокирующим, и обычно используется метод подключения к базе данных.SQLAlchemy, Джангоormи т.п. Если он развернут на нашем сервере API, таком как flask, django и т. д., это вызовет блокировку в одном потоке, и необходимо использовать многопроцессорность, такую как библиотека gevent, многопоточная модель и т. д., чтобы иметь возможность чтобы позволить параллелизм.
Ниже мы представляем некоторые библиотеки вызовов ввода-вывода, которые реализуют aio (блокировку fei) на основе широко используемых библиотек вызовов ввода-вывода.
Часто используемые библиотеки асинхронного ввода/вывода
Предположим, мы реализуем сон с помощью сопрограмм:
import asyncio
import time
async def do_some_work(x):
print('Waiting: ', x)
time.sleep(x)
return 'Done after {}s'.format(x)
coroutine = do_some_work(2)
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
Хотя в этом методе определен асинхронный метод, добиться неблокировки невозможно.Когда программа запускается в time.sleep, она все равно будет блокироваться на x секунд.Невозможно использовать await для достижения неблокировки, поэтому мы необходимо изменить time.sleep для ожидания asyncio. Sleep(x) может достичь неблокирующего эффекта. Точно так же выполнение библиотек, таких как urllib, запросы и SQLAlchemy, блокируется. Когда поток запускается к соответствующему методу вызова ввода-вывода, он всегда будет ждать возврата выполнения. Единственный способ решить эту проблему — мульти- threading И сопрограммы, многопоточность в Python ограничена по эффективности и сложна в управлении из-за существования блокировок GIL, поэтому для ее решения рекомендуется использовать сопрограммы. Вот несколько библиотек http и mysql, которые реализуют aio, от сторонних разработчиков, подробные методы реализации можно посмотреть на github.
библиотека aiohttp
import asyncio
from aiohttp import ClientSession
url = "https://www.baidu.com/{}"
async def hello(url):
async with ClientSession() as session:
async with session.get(url) as response:
response = await response.read()
print(response)
if __name__ == '__main__':
coroutine = hello(url)
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
В приведенном выше примере могут быть реализованы неблокирующие http-запросы.Когда несколько задач сопрограммы помещаются в метод loop.run_until_complete() для выполнения, когда coroutine1 запускается для ожидания response.read(), она входит в ожидание, но в то же время coroutine2 может начать выполнение Вместо того, чтобы ждать сопрограммы1, она не блокируется. Точно так же есть aiomysql, aioredis и т.д.
библиотека aiomysql
import asyncio
import aiomysql
async def test_example(loop):
pool = await aiomysql.create_pool(host='127.0.0.1', port=3306,
user='root', password='',
db='mysql', loop=loop)
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT 42;")
print(cur.description)
(r,) = await cur.fetchone()
assert r == 42
pool.close()
await pool.wait_closed()
loop = asyncio.get_event_loop()
loop.run_until_complete(test_example(loop))
С помощью библиотеки aiomysql можно создавать таблицы базы данных и выполнять CRUD-операции без блокировки, ведь сам mysql поддерживает многопоточность.
Про асинхронность и ожидание
Как асинхронность и ожидание реализованы в python? На самом деле, в старой версии python ключевое слово yield использовалось для инкапсуляции метода в генератор, так что каждый метод для реализации сопрограммы инкапсулировался функцией-декоратором @coroutine. async и await — это новый синтаксис после версии 3.5, но реализация аналогична. Например, в методе asyncio.sleep(), реализованном выше, когда несколько задач, таких как сопрограмма1 и сопрограмма2, передаются в цикле. и выполнить coroutine2 , и весь метод loop.run_until_complete() будет проходить через значение генератора и, наконец, получать результат каждой сопрограммы, а затем выполнять следующие шаги по отдельности, таким образом достигая неблокирующего эффекта. Однако этот метод по-прежнему не может использовать преимущества многоядерных процессоров, поэтому лучший способ развертывания серверов по-прежнемуМультипроцесс + сопрограммаПуть
Асинхронный сервер в сочетании с фреймворком Tornado
После разговора о методе реализации сопрограммы, который python работает непосредственно в режиме сценария, давайте посмотрим, как реализовать работу в режиме сопрограммы, если это сервер. Часто используемые фреймворки Python, такие как flask и Django, не могут обеспечить неблокировку при запуске служб API и прослушивании портов, поэтому методы развертывания flask и Django часто используют многопоточность для повышения эффективности параллелизма. Появившийся в последние годы фреймворк Tornado реализован с использованием механизма мультиплексирования ввода-вывода epoll, который называется uvloop. На нижнем уровне инкапсуляция цикла в начале постепенно переросла в инкапсуляцию на основе библиотеки asyncio, появившейся после python3.4, поэтому фреймворк Tornado управляется событиями как сервис API.
базовая реализация
import tornado.web
import tornado.httpserver
import tornado.ioloop
class IndexPageHandler(tornado.web.RequestHandler):
def get(self):
self.render('tornado_index.html')
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', IndexPageHandler),
]
settings = {"template_path": "templates/"}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
app = Application()
server = tornado.httpserver.HTTPServer(app)
server.listen(5000)
tornado.ioloop.IOLoop.instance().start()
Приведенный выше метод запуска фреймворка tornado использует предыдущий метод ioloop.В этом методе, если асинхронный метод get определен в нашем контроллере IndexPageHandler, ожидание внутри не может обеспечить неблокировку. Поэтому нам нужно использовать следующий механизм цикла событий asyncio+uvloop для достижения неблокировки:
неблокирующая реализация
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.platform.asyncio as tornado_asyncio
import asyncio
import uvloop
class IndexPageHandler(tornado.web.RequestHandler):
def get(self):
self.render('tornado_index.html')
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', IndexPageHandler),
]
settings = {"template_path": "templates/"}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
tornado_asyncio.AsyncIOMainLoop().install()
app = Application()
server = tornado.httpserver.HTTPServer(app)
server.listen(5000)
asyncio.get_event_loop().run_forever()
Запустив Tornado таким образом, мы можем определить асинхронные методы в нашем IndexPageHandler и других обработчиках, а затем ввести нашу библиотеку aio и использовать метод await для неблокирующего времени.
Рекомендации по развертыванию Tornado
- Управляйте фреймворком Tornado неблокирующим образом;
- Запустите службу API в многопроцессорном режиме;
- Если вам нужно развернуть изоляцию среды или быстрое расширение, рекомендуется использовать Docker.