一,python使用协程
消费者不用多说,会停在yield这里。之前只知道当调用next()时,会继续执行。到了协程这里,当调用了send(msg)时,这个生成器会收到这个msg并继续执行。yield 如果带上了r,又会把r返回给调用了send(msg)的人。
def consumer():
r = ''
while True:
n = yield r
print('消费者:消费 %s' % n)
r = '消费完毕'
def produce(c):
c.send(None) #启动生成器必须先调用一个 send(None) 要不然这个生成器就跑不起来
n = 0
while n<5:
n = n + 1
print('生产者: 生产 %s' % n)
r = c.send(n)
print('生产者: 消费者返回 %s' % r)
c.close() #关闭这个生成器
c = consumer()
produce(c)
Output:
生产者: 生产 1
消费者:消费 1
生产者: 消费者返回 消费完毕
生产者: 生产 2
消费者:消费 2
生产者: 消费者返回 消费完毕
生产者: 生产 3
消费者:消费 3
生产者: 消费者返回 消费完毕
生产者: 生产 4
消费者:消费 4
生产者: 消费者返回 消费完毕
生产者: 生产 5
消费者:消费 5
生产者: 消费者返回 消费完毕
上面的过程其实不太好理解,下面简化以下代码:
def consumer():
r = ''
while True:
n = yield #把r去掉了
print('消费者:消费 %s' % n)
r = '消费完毕'
def produce(c):
c.send(None) #启动生成器必须先调用一个 send(None)
n = 0
while n<5:
n = n + 1
print('生产者: 生产 %s' % n)
r = c.send(n)
print('生产者: 消费者返回 %s' % r)
c.close()
c = consumer()
produce(c)
Output:
生产者: 生产 1
消费者:消费 1
生产者: 消费者返回 None
生产者: 生产 2
消费者:消费 2
生产者: 消费者返回 None
生产者: 生产 3
消费者:消费 3
生产者: 消费者返回 None
生产者: 生产 4
消费者:消费 4
生产者: 消费者返回 None
生产者: 生产 5
消费者:消费 5
生产者: 消费者返回 None
所以这里就能更好的理解 n = yield r 和 r = c.send(n) 之间的调用关系。
- generator 启动之后会阻塞在yield r这里等待获取返回值n (执行从consumer转到produce)
- 当调用了c.send(n) 之后,yield就会获取到n继续执行 (执行从produce转到consumer)
- 当再次执行到yield r之后 返回r。 这是c.send(n)就获取到了返回值r (执行从consumer转到producer)
二,异步I/O asyncio
协程的执行是需要我们自己去控制如何协作的,就像前面演示的那样。asyncio借助协程封装了一个异步I/O模块,把任务作为一个协程放进消息循环中,当执行到耗时任务时,就在消息循环中选另一个任务执行。具体如下,
import asyncio
import time, threading
@asyncio.coroutine
def task():
print('线程%s 开始时间%s' % (threading.currentThread(), time.time()))
yield from asyncio.sleep(5) #yield from 异步执行 不会等待,而是在消息循环中选另一个去执行
print('线程%s 结束时间%s' % (threading.currentThread(), time.time()))
tasks = [task(), task()]
loop = asyncio.get_event_loop() #异步I/O的核心就是消息循环
loop.run_until_complete(asyncio.wait(tasks)) #把任务丢进这个消息循环去执行
loop.close()
Output:
线程<_MainThread(MainThread, started 12016)> 开始时间1545976887.4829051
线程<_MainThread(MainThread, started 12016)> 开始时间1545976887.4833813
线程<_MainThread(MainThread, started 12016)> 结束时间1545976892.4837344
线程<_MainThread(MainThread, started 12016)> 结束时间1545976892.4837344
可以看到,两个任务都在主线程中执行,但是,一共只睡了5秒。完成了单线中的并发。
python3.5之后,引入了新语法,async和await。替换@asyncio.coroutine和yield from。简化书写。
需要注意,
- async只能在def with for之前使用,把它们编程协程。
- await是调用另一个协程的意思
import asyncio
import time, threading
async def task():
print('线程%s 开始时间%s' % (threading.currentThread(), time.time()))
await asyncio.sleep(5) #yield from 异步执行 不会等待,而是在消息循环中选另一个去执行
print('线程%s 结束时间%s' % (threading.currentThread(), time.time()))
tasks = [task(), task()]
loop = asyncio.get_event_loop() #异步I/O的核心就是消息循环
loop.run_until_complete(asyncio.wait(tasks)) #把任务丢进这个消息循环去执行
loop.close()
Output:
线程<_MainThread(MainThread, started 9828)> 开始时间1545977288.9284196
线程<_MainThread(MainThread, started 9828)> 开始时间1545977288.9284196
线程<_MainThread(MainThread, started 9828)> 结束时间1545977293.939544
线程<_MainThread(MainThread, started 9828)> 结束时间1545977293.939544
三,在服务器端应用异步I/O aiohttp
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的第三方HTTP框架,是requests的异步版本。
客户端方面:
HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。举个例子,当多个用户同时想向服务器发起http连接,每个连接是需要耗时的,如果使用requests,那么这些用户在连接完成前就只能等待,如果使用aiohttp去发起连接,那么发起连接后,就可以去处理别的事情。
再来一个更直观的例子,在一个线程里,用requests循环进行20次http连接,和用aiohttp循环进行20次http连接,后者耗时肯定要少很多。
服务端方面:aiohttp还可以用来开发web应用
先来看看服务器对待请求的策略:
同步服务器:
- 服务器只有一个进程和一个线程,每个请求都顺序处理
- 多进程:来一个请求就开起一个新进程去处理 稳定 开销大
- 多线程:来一个请求就开起一个新线程去处理 开销小 不稳定
异步服务器:
- 单线程+协程 稳定 开销小
aiohttp开发的就是异步服务器。下面是简单的使用。附上一篇aiohttp中文文档
from aiohttp import web
import asyncio
async def index(request):
await asyncio.sleep(5) #访问这个页面要耗时5秒
return web.Response(body=b'<h1>Index</h1>', content_type='text/html')
async def hello(request):
await asyncio.sleep(5) #访问这个页面也要耗时5秒
text = '<h1>hello, %s!</h1>' % request.match_info['name']
return web.Response(body=text.encode('utf-8'), content_type='text/html')
def init():
app = web.Application() #创建单线程+协程的服务器
app.router.add_route('GET', '/', index) #添加路由
app.router.add_route('GET', '/hello/{name}', hello) #添加路由
web.run_app(app, host='127.0.0.1', port=8000) #启动
print('Server started at http://127.0.0.1:8000...')
init()