python异步编程

https://www.ddpool.cn/article/51930.html

fastapi理解:https://www.jianshu.com/p/66bb458ac530

PS:

因为GIL的存在,所以Python的多线程在CPU密集的任务下显得无力,但是对于IO密集的任务,多线程还是足以发挥多线程的优势的,而异步也是为了应对IO密集的任务,所以两者是一个可以相互替代的方案,因为设计的不同,理论上异步要比多线程快,因为异步的花销更少, 因为不需要额外系统申请额外的内存,而线程的创建跟系统有关,需要分配一定量的内存,一般是几兆,比如linux默认是8MB

1.概念

python提供了asyncio模块来支持异步编程,其中涉及到coroutinesevent loopsfutures三个重要概念

coroutines(字面意思:协程):

是一个可以在执行中暂停并切换到event loops执行流程的特殊类型的函数

event loops:

事件循环
1.2不同python版本的几种写法
  • python3.5及其以上(如4.3写法)

    loop = asyncio.get_event_loop()
    loop.run_until_complete(协程对象或者task列表)
    
  • python3.7及其以上

    asyncio.run(一般是主协程函数对象)
    

2.协程

协程(Coroutine),可称为微线程,是一种用户态内的上下文切换技术,其实就是通过一个线程来是实现代码块的相互切换执行。

实现协程的几种方法:

  • greenlet,早期模块
  • yield关键词
  • asyncio装饰器(python3.4)
  • async、await关键字(python3.5)
2.1 greenlet
from greenlet import greenlet

def func():
    print(1)
    gr2.switch()
    print(2)
    gr2.switch()

def func2():
    print(3)
    gr1.switch()
    print(4)
    
gr1 = greenlet(func)
gr2 = greenlet(func2)

gr1.switch()

执行结果:1,3,2,4

2.2 yield

通过生成器实现切换,很少用

def func1():
    yield 1
    yield from func2()
    yield 2
def func2():
    yield 3
    yield 4

f1 = func1()
for item in f1:
    print(item)
2.3 asyncio

上面两种都是手动,此处是自动,python3.4之后的版本通过装饰器来实现,遇到io自动切换。

协程是带有装饰器的函数,将其加入tasks中,再将tasks加入loop事件循环中。

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)
    print(2)


@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2)
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

2.4 async & await关键字

python3.5版本之后,和上一步实现逻辑一样

import asyncio

async def func1():
    print(1)
    await asyncio.sleep(2)
    print(2)

async def func2():
    print(3)
    await asyncio.sleep(2)
    print(4)


tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))


3.爬虫例子

aiohttp是异步请求

from asyncio import tasks
from multiprocessing.connection import wait
import aiohttp
import asyncio

async def fetch(session,url):
    print("发送请求:",url)
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        # file_name = url.rsplit("_")[-1]
        file_name = "s.jpg"
        with open(file_name,mode='wb') as file_object:
            file_object.write(content)

async def main():
    async with aiohttp.ClientSession() as session:
        url_lst = [
            r'https://pic.qqtn.com/up/2017-11/15102069107000700.jpg',
            r'https://pic.qqtn.com/up/2017-11/15102069107000700.jpg'
        ]

        tasks = [ asyncio.create_task(fetch(session, url)) for url in url_lst]

        await asyncio.wait(tasks)
if __name__ == '__main__':
    asyncio.run(main())

4.概念+上手

4.1事件循环

理解为一个死循环,去检测并执行某些代码

任务列表 = [任务1,任务2,任务3...]

while True:
    可执行的任务列表,已完成的任务列表 = 去任务列表中检查获取所有的可执行任务,将可执行和已完成的任务返回
    
    for 就绪任务 in 可执行的任务列表:
        执行就绪的任务
        
    for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除已完成的任务
        

import asyncio

#去生成或获取一个事件循环
loop = asyncio.get_event_loop()

#将任务列表放进事件循环中
loop.run_until_complete(协程对象)

4.2协程函数+对象

协程函数,定义函数时async def 函数名

协程对象,协程函数()

async def func1():
    pass

result = func1()

注意:执行协程函数创建协程对象,函数内部代码不会执行

要想运行协程函数内部代码,必须要加入事件循环

import asyncio 

async def func():
    print("xxx")
    
result = func()
#loop = asyncio.get_event_loop()
#loop.run_until_complete( result )
asyncio.run( result ) #python3.7,上面是以前写法

4.3await

await + 可等待的对象(协程对象、Future、Task对象)

示例一:await后只跟对象,事件循环是在协程对象之间来回切换,当await存在,其下方代码会等待await所指向的执行完成之后再执行,因为都在一个函数对象中

import asyncio
async def func():
    print('func begin')
    await asyncio.sleep(3)
    print('func end')


async def main():
    print('main begin')
    await func()   
    await func()
    print('main end')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete( main() )

主线程中有多个await且后跟协程对象,会依次执行,后跟task对象会并发执行

4.4Task对象

示例一:

import asyncio
async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"

async def main():
    print("main开始")
    task1 = asyncio.create_task(func())
    task2 = asyncio.create_task(func())

    print("main结束")
    #此处的await和4.3中含义不一样,此处不会阻塞,但是awati后跟协程对象会阻塞
    ret1 = await task1   
    ret2 = await task2

    print(ret1, ret2)

asyncio.run(main())

示例二:

import asyncio
async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"

async def main():
    print("main开始")

    task_lst = [
        asyncio.create_task(func(),name="n1"), #name为task名称
        asyncio.create_task(func(),name="n2")
    ]

    print("main结束")
    # 一般timeout设置为None,如果设置的时间内任务没有完成,则只会返回pending(done返回一个集合)
    done, pending = await asyncio.wait(task_lst,timeout=None)
    print(done)

asyncio.run(main())

示例三:

import asyncio

async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"

task_lst = [
    func(),
    func()
]

done,pending = asyncio.run(asyncio.wait(task_lst))
print(done)

4.5asyncio.Future对象

Future的作用时终止loop的循环,task对象继承于它

import asyncio

async def set_after(fut):
    await asyncio.sleep(2)
    fut.set_result('66')

async def main():
    # 获取当前事件循环
    loop = asyncio.get_running_loop()
	# 创建一个任务(Future对象),没绑定任何行为,则这个任务不知道什么时间结束
    fut = loop.create_future()
	# 创建一个任务(Task对象),绑定了set_after函数,函数内部会在2s之后给fut赋值
    # 即手动设置future任务的最终结果,那么fut就可以结束了
    await loop.create_task( set_after( fut))
	# 等待Future对象获取最终结果,否则一直等下去
    data = await fut

    print(data)

asyncio.run(main())

4.6concurrent.futures.Future对象

使用线程池、进程池实现异步操作时要用到的对象

from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExecutor

def func(value):
    time.sleep(1)
    print(value)
    
pool = ThreadPoolExecutor(max_workers=5)

for i in range(10):
    fut = pool.submit(func,i)
    print(fut)

什么场景使用协程+线程池

  • 当异步协程编程碰到某一个不支持线程、进程、协程的模块时要用到
  • 协程操作redis、mysql会有相关支持的模块aioredis、aiomysql,如果其他的数据库没有支持异步的模块,就需要使用协程+进程池、线程池,只是会增加使用一些资源
import time
import asyncio
import concurrent.futures

def func():
    # 某个耗时操作
    time.sleep(2)
    return "xx"

async def main():
    loop = asyncio.get_running_loop()
    #run_in_executor(默认ThreadPoolExecutor)
    #第一步:内部先调用ThreadPoolExecutor的submit方法去线程池中申请一个线程去执行func函数,并返回一个concurrent.futures.Future对象
    #第二步:调用asyncio.wrap_future将concurrent.futures.Future对象转为asyncio.Future对象,因为concurrent.futures.Future不支持await方法
    fut = loop.run_in_executor(None, func)
    ret = await fut
    print('default thread pool')
    
    
    #with concurrent.futures.ThreadPoolExecutor() as pool:
    #    result = await loop.run_in_executor(pool,func)
    #    print('default thread pool')
    #with concurrent.futures.ProcessPoolExecutor() as pool:
    #    result = await loop.run_in_executor(pool,func)
    #    print('custom process pool',result)
    
asyncio.run(main())

对应爬虫案例(requests模块不支持异步):

import asyncio
import requests

async def download_image(url):
    print("开始下载:",url)
    loop = asyncio.get_event_loop()

    # requests模块默认不支持异步操作,所以使用线程池来配合实现
    future = loop.run_in_executor(None, requests.get, url)

    response = await future
    print("下载完成")
    # 图片保存到本地
    file_name = url.rsplit('_')[-1]
    with open(file_name, mode='wb') as file_object:
        file_object.write(response.content)


if __name__ == '__main__':
    url_lst = [

    ]
    tasks = [download_image(url) for url in url_lst]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))

4.7异步迭代器

什么是异步迭代器

实现了__aiter__()__anext()__方法的对象。async_for会处理异步迭代器的__anext__()方法所返回的可等待对象,直到引发一个StopAsyncIteration异常。

什么是异步可迭代对象

可在async_for语句中被使用的对象。必须通过__aiter__()方法返回一个asynchronous_iterator

import asyncio

class Reader(object):
    """ 自定义异步迭代器(同时也是异步可迭代对象) """
    def __init__(self):
        self.count = 0
        
    async def readline(self):
        self.count += 1
        if self.count == 100:
            return None
        return self.count
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        val = await self.readline()
        if val == None:
            raise StopAsyncIteration
        return val

# 在异步方法中写for循环没问题
async def func():
    obj = Reader()
    async for item in obj:
        print(item)

asyncio.run( func() )
    

4.8异步上下文管理

此种对象通过定义__aenter__()__aexit()__方法。

import asyncio 

class AsyncContextManager:
    def __init__(self):
        self.conn = conn
    
    async def do_something(self):
        # 异步操作数据库
        return 666
    async def __aenter__(self):
        # 异步连接数据库
        self.conn = await asyncio.sleep(1)
        return self
    async def __aexit__(self,exc_type,exc,tb):
        # 异步关闭数据库
        await asyncio.sleep(1)
async def func():
    async with AsyncContextManager() as f:
        result = await f.do_something()
        print(result)
asyncio.run(func())

4.9.uvloop

uvloop是asyncio中默认事件循环的替代方案,它更快

使用:

# pip install uvloop
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

#编写asyncio的代码,与之前代码一致

#内部的事件循环自动转为uvloop
asyncio.run(...)

ps:django3.x(asgi->uvicron)和fastapi底层都是使用了uvloop

5.fastapi

import asyncio
import uvicorn
import aioredis
from aioredis import Redis
from fastapi import FastAPI
import time

app = FastAPI()

REDIS_POOL = aioredis.ConnectionPool('redis://127.0.0.1:6379',minsize=1,maxsize=10)

@app.get("/")
def index():
    print("请求来了")
    time.sleep(5)
    return {"message":"hello"}

@app.get("/red")
async def red():
    """异步操作接口"""
    print("请求来了")
    await asyncio.sleep(10)
    # #连接池获取一个连接
    # conn = await REDIS_POOL.acquire()
    # redis = Redis(conn)

    # #设置值
    # await redis.hmset_dict('car',key1=1,key2=2)

    # #读取值
    # result = await redis.hgetall('car',encoding='utf-8')
    # print(result)
    # # 连接归还连接池
    # REDIS_POOL.release(conn)

    # return result
    return 'xx' 


if __name__ == '__main__':
    uvicorn.run("fastapitest:app",host='127.0.0.1',port=5000,log_level="info")

压测例子:

fastapi最精彩的地方:

async函数会放到event loop中执行。
那么,普通的函数会会放到thread pool中 ,线程池又跟电脑配置有关(线程池中数量:核数*5)

from fastapi import FastAPI
import time
import asyncio
import os

app = FastAPI()

@app.get("/async_slowest")
async def async_slowest():           #执行最慢,串行执行
   time.sleep(1)
   return {"message": "async mode but use sync sleep"}
  
@app.get("/async_sleep_in_thread")
async def async_sleep_in_thread():
   loop = asyncio.get_event_loop()
   await loop.run_in_executor(None, time.sleep, 1)
   return {"message": "sleep run in thread pool"}
  
@app.get("/async_sleep")
async def async_sleep():            #执行最快,协程之间切换消费较少资源
   await asyncio.sleep(1)
   return {"message": "async mode sleep"}

@app.get("/sync")
def sync_sleep():                   #执行第三快,普通函数会放到thread pool中
   time.sleep(1)
   return {"message": "sync, but run in thread pool"}

if __name__ == '__main__':
    uvicorn.run("fastapitest:app",host='127.0.0.1',port=5000,log_level="info")

6.压测工具

ab、hey

posted @ 2023-02-26 16:39  MISF  阅读(152)  评论(0编辑  收藏  举报
     JS过度和变形效果演示   
  
    html5.png