异步编程之协程(asyncio模块)
协程:协程是在用户空间,基于生成器(yield),在单线程内(而非多线程的操作系统调度),实现的一种非抢占式调度;当一个协程主动让出控制权,另一个协程才会被调度。。python3.4引入。
协程在单线程内完成,没有多线程切换带来的开销
单线程内调度,不需要锁机制
多CPU下,多进程+协程,实现进程并发,同时发挥协程在单进程的优势。
1 asyncio模块
通过asyncio模块实现协程,asyncio模块其实是一个框架,包括异步IO,事件循环、协程、任务等内容。
1、问题引入: 以下示例,在单线程内,通过生成器(yield语句)完成任务调度,让两个函数交替执行,看似并行。这种调度是在用户空间由用户设计的,而不是通过多线程或多进程由操作系统调度。协程就是基于yield实现的。
def foo1():
for i in range(3):
print("foo1:{}".format(i))
yield
def foo2():
for i in range(3):
print("foo1:{}".format(i))
yield
a = foo1()
b = foo2()
for i in range(3):
next(a)
next(b)
2、事件循环: 事件循环是asyncio提供的核心运行机制
理解示例:
import asyncio
from tool.show_thread import show_thread
# 一个生成器函数,在上门加一个协程装饰器,它就变成了一个协程函数。
@asyncio.coroutine # python3.4版本使用协程装饰器 + yield。
def sleep(x):
for i in range(3):
print("sleep {}".format(i))
yield from asyncio.sleep(x)
async def new_sleep(x): # python3.5开始,语言通过`async def`关键字原生支持定义协程函数。通过`async def`关键字定义的协程函数,可以使用`asyncio.iscoroutinefunction()`判断是不是协程函数;使用`asyncio.iscoroutine`判断是不是协程对象。
for i in range(3):
print("new sleep:{}".format(i))
await asyncio.sleep(x) # 使用`async def`,则函数内就不能用yield了,yield语句用await来替换,也就是说这种语法有它自己的关键字了,虽然原理都一样。
if __name__ == '__main__':
show_thread()
# 事件循环是asyncio提供的核心运行机制
loop = asyncio.get_event_loop() # 获取一个事件循环对象对象
print("is coroutine function:{}.".format(asyncio.iscoroutinefunction(new_sleep)))
task = [sleep(3), new_sleep(3)]
# loop.run_until_complete(sleep(3)) # 运行至指定的future对象运行完成
# loop.is_running() # 事件是否在运行
# loop.stop() # 停止事件
loop.run_until_complete(asyncio.wait(task)) # asyncio.wait(task),task是一个协程函数对象列表。把所有任务丢进列表中,就实现交替运行
loop.close() # 关闭事件循环对象
2 使用协程实现群聊
import asyncio
async def my_handle(client_reader, client_writer):
while True:
data = await client_reader.read(1024)
print("client reader:{}".format(dir(client_reader)))
print("client writer:{}".format(dir(client_writer)))
client = client_writer.get_extra_info("peername")
msg = "{} your message {}".format(client, data.decode()).encode()
client_writer.write(msg)
await client_writer.drain()
def my_chat(*args):
ip = "127.0.0.1"
port = 9998
loop = asyncio.get_event_loop()
chat = asyncio.start_server(my_handle, ip, port, loop=loop)
server = loop.run_until_complete(chat)
print("current socket:{}".format(server))
try:
loop.run_forever()
except Exception as e:
print(e)
pass
finally:
server.close()
loop.close()
if __name__ == '__main__':
my_chat()
3 aiohttp模块
使用aiohttp模块实现http serve
r和http client
示例:
import asyncio
from aiohttp import web
from aiohttp import ClientSession
class MyServer:
@classmethod
async def get_html_index_handle(cls, req: web.Request):
return web.Response(text=req.path, status=201)
@classmethod
async def get_html_handle(cls, req: web.Request):
print(req.match_info)
print(req.query_string) # http://127.0.0.1:9998/1?name=12301
return web.Response(text=req.match_info.get("id", "0000"), status=200)
def implement_server(self):
app = web.Application()
app.router.add_get("/", self.get_html_index_handle) # http://127.0.0.1:9998/
app.router.add_get("/{id}", self.get_html_handle) # http://127.0.0.1:9998/12301
web.run_app(app, host="127.0.0.1", port=9998)
class MyClient:
@classmethod
async def get_html(cls, url: str):
async with ClientSession() as session:
async with session.get(url) as res:
print(res.status)
print(await res.text())
def implement_client(self):
url = "http://127.0.0.1:9998/ziroom-web"
loop = asyncio.get_event_loop()
loop.run_until_complete(self.get_html(url))
loop.close()