Python异步编程
异步编程:请求后,并没有得到最终结果。而后续是否得到最终结果未可知。
asyncio
3.4版本加入到标准库。
asyncio底层基于selectors实现, 看似库,实际上是框架,也就是一系列架子已经搭建完毕,定义好了流程。它包含异步IO、事件循环、协程、task等内容。
要实现让函数交替运行,除了多线程的方式,还有什么方法?
def a(x=3):
for i in range(x):
out = "a.x {}".format(i)
yield out
def b(x=3):
for i in range(x):
out = "b.x {}".format(i)
yield out # 定义了生成器函数,需要产生生成器对象才能使用
a = a() # 必须有生成器对象,可以使用生成器表达式或者生成器函数。此处为生成器函数生成的生成器对象
b = b() # 而且往往都是生成器函数,函数才可写复杂的逻辑来实现
for i in range(3): # 必须有循环,否则跑一边就结束了
print(next(a))
print(next(b))
如果遇到耗时的过程,则yield让出执行权力,切换到其他函数执行。这是一种被迫让出,一旦执行到yield,必须让出,这依靠代码的控制逻辑完成。
如此看似并行执行,实际上是串行。如果切换很快,和多线程很像。
无论是进程切换,还是线程切换都是有开销的。
如此可在线程内完成功能函数的切换,而且是单线程的,不会出现如多线程争抢资源的情况。因此,性能较好,且不会出现多线程的同步问题。
在一个线程中,完成函数切换,效率较高。
注意:生成器函数中,一般无return语句,因为一旦显式return,就无法往下执行了,但隐藏了return None
因此,异步的条件有两个:
- 利用循环
- yield让出控制权
事件循环
事件循环是asyncio提供的核心运行机制。
名称 | 含义 |
asyncio.get_event_loop() | 返回一个事件循环对象,是asyncio.BaseEventLoop的实例。相当于while true或for循环 |
AbstractEventLoop.stop() | 停止运行事件循环 |
AbstractEventLoop.run_forever() | 一直运行,直到stop() |
AbstractEventLoop.run_until_complete(future) | 运行直到Future对象运行完成,异步的处理方案 |
AbstractEventLoop.close() | 关闭事件循环 |
AbstractEventLoop.is_running() | 返回事件循环是否运行 |
协程 Coroution
- 协程既非进程,也不是线程。它是用户空间调度完成并发处理的一种方式
- 进程、线程由操作系统完成调度,而协程是线程内完成调度。它无需更多的线程,因此也无线程切换带来的开销
- 协程是非抢占式调度,只有一个协程主动让出控制权,另一个协程才会被调度
- 协程无需使用锁机制,因为在同一线程内执行
- 多CPU下,可使用多进程和协程配合,既能发挥进程并发的优势,又能发挥协程在单线程中的优势
- Python中协程基于生成器
协程的使用
3.4引入asyncio
使用装饰器
import asyncio
@asyncio.coroutine
def a(x=3):
for i in range(x):
out = "a.x {}".format(i)
print(out)
yield # 生成器函数
@asyncio.coroutine
def b(x=3):
for i in range(x):
out = "b.x {}".format(i)
print(out)
yield
loop = asyncio.get_event_loop() # 使用工厂函数get一个事件循环,而无需for了
tasks = [a(), b()] # a()为生成器对象
loop.run_until_complete(asyncio.wait(tasks)) # 把生成器对象放到循环中。如果调度任务有多个,则使用asyncio.wait()需要传递一个可迭代对象,一般为列表
loop.close()
输出:
b.x 0
a.x 0
b.x 1
a.x 1
b.x 2
a.x 2
将生成器函数转换成协程函数,就可以在事件循环中执行了。
使用协程语法
3.5版本开始,Python提供关键字async、await,在语言上原生支持协程。使用装饰器或者原生语法都可以,如果使用async,则一定使用await而不能yield!
import asyncio
async def a(x=3): # async 引领异步定义,异步调用,创造一个协程
for i in range(x):
out = "a.x {}".format(i)
print(out)
await asyncio.sleep(0.0001) # 使用awiat和yield区分开来,使得协程和生成器区分开来
async def b(x=3):
for i in range(x):
out = "b.x {}".format(i)
print(out)
await asyncio.sleep(0.0001) # 设置等待时长,此时进程并不是真睡。如果使用time.sleep,则整个进程都睡了
# 构成大循环
loop = asyncio.get_event_loop()
tasks = [a(),b()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
输出:
a.x 0
b.x 0
a.x 1
b.x 1
a.x 2
b.x 2
async def用于定义协程函数,iscoroutinefunction()返回True。协程函数中可以不包含await、async关键字,但不能使用yield关键字
如同生成器函数调用,返回生成器对象一样,协程函数调用也会返回一个对象成为协程对象,iscoroutine()返回True,则是协程。
EchoServer
import asyncio
async def handle(reader, writer): # 把socket请求当作参数传递进来,reader:STREAM_READER,writer:STREAM_WRITER
while True:
data = await reader.read(1024) # 从缓冲区读,消耗时间,因此它是一个生成器,yield from数据出来。是一个生成器对象。
print(dir(reader))
print(dir(writer))
client = writer.get_extra_info('peername') # 获取客户的信息
message = "{} Your msg {}".format(client,data.decode()).encode() # 打印客户的信息和读取到的数据
writer.write(message) # 写数据到缓冲区
await writer.drain() # 类似flush,发送数据到客户的,消耗时间,异步之
loop = asyncio.get_event_loop()
ip = '127.0.0.1'
port = 9999
crt = asyncio.start_server(handle, ip, port, loop=loop) # handle定义处理请求,它是异步处理的
server = loop.run_until_complete(crt)
print(server)
try:
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
server.close()
loop.close()
然后就可以写客户端去访问了。
AIOHTTP库
安装:pip install aiohttp
HTTP Server
from aiohttp import web
async def indexhandle(request:web.Request): # http包装后的请求
return web.Response(text=request.path, status=201)
async def handle(request:web.Request):
print(request.match_info)
print(request.query_string)
return web.Response(text=request.match_info.get('id','0000'), status=200) # 响应,取出id的值返回给客户的
app = web.Application() # 定义web服务端
app.router.add_get("/", indexhandle)
app.router.add_get("/{id}", handle) # /{id},其中id为key,传递的值为值
web.run_app(app, host='0.0.0.0',port=9999)
HTTP Client
import asyncio
from aiohttp import ClientSession
async def get_html(url:str):
async with ClientSession() as session:
async with session.get(url) as res:
print(res.status)
print(await res.text())
url = 'http://127.0.0.1:9999'
loop = asyncio.get_event_loop()
loop.run_until_complete(get_html(url))
loop.close()