Python реализует эффективные неблокирующие вызовы ввода-вывода для потоков.

Python
Python реализует эффективные неблокирующие вызовы ввода-вывода для потоков.

предисловие

Многие люди уже знают о сопрограммах 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

  1. Управляйте фреймворком Tornado неблокирующим образом;
  2. Запустите службу API в многопроцессорном режиме;
  3. Если вам нужно развернуть изоляцию среды или быстрое расширение, рекомендуется использовать Docker.

использованная литература

  1. blog.CSDN.net/Брюс Вонг05…
  2. Краткое описание.com/fear/no 5 oh 347 no 3 ah...
  3. nuggets.capable/post/684490…