一.协程
1.1简介
协程(Coroutine)又称微线程,程序中的函数,又称为子程序,在所有语言中都是层级调用,例如在A函数中调用了B,在B的执行过程中调用了C,C执行结束返回结果到B,B得到结果继续执行到结束返回结果到A,A拿到B返回的结果继续执行到结束,子程序的调用是通过栈实现的,子程序是一个入口,一次返回,顺序调用;而协程模式在执行过程中可以中断一个子程序去执行另一个子程序,在适当的时候又返回来接着执行这个子程序,这个中断不是因子程序内部调用其它子程序来切换,而是类似CPU自己去切换的;
例如运行下面两个子程序:
def A():
print('1')
print('2')
print('3')
def B():
print('x')
print('y')
print('z')
如果由协程执行结果可能为:
1
2
x
3
y
z
Python里的generator就是对协程的支持,在generator中可以通过for循环来迭代,也可以不断调用next()方法来取由yield语句返回的下一个值。
1.2协程和多线程
a.协程的切换是由程序自身控制,而不是线程切换,省了多线程的线程切换的开销,和多线程相比,线程越多,协程优势越明显。
b.协程只有一个线程,避免了多线程里同时操作一个变量冲突问题,也避免了死锁。
1.3生产者和消费者
用多线程方式实现生产者和消费者模型,一般是一个线程生成,一个线程消费消息,通过线程间wait和notify来控制生产和消费逻辑;
如果用协程,可以用yield语句跳转到消费者去执行,消费者执行完毕后,切换回生产者继续生产,效率更高,一个线程不容易出错;
def comsumer():
r = ''
while True:
n = yield r
if not n:
return
print('consumer---', n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('produce---', n)
r = c.send(n)
print('consumer return', r)
c.close()
c = comsumer()
produce(c)
---
produce--- 1
consumer--- 1
consumer return 200 OK
produce--- 2
consumer--- 2
consumer return 200 OK
produce--- 3
consumer--- 3
consumer return 200 OK
produce--- 4
consumer--- 4
consumer return 200 OK
produce--- 5
consumer--- 5
consumer return 200 OK
上面例子中,consumer是一个generator,把一个consumer传入produce;
a.先通过c.send(None)启动generator;
b.然后等produce生成了内容就通过c.send(n)传到consumer里去;
c.consumer通过yield拿到传过来的内容进行处理,再把处理的结果内容作为返回值通过yield返回给produce;
d.produce拿到处理的结果,又继续生成下一条消息传给consumer;
e.直到produce不想生产了,调用c.close()关闭consumer,流程结束。
整个流程无锁,是在一个单线程里完成的;
由于协程是一个线程执行,想要完全利用多核CPU,最简单的方法是利用多进程+协程,既充分利用多核,又发挥了协程的高效率。
1.4asyncio实现异步IO
asyncio是python3.4版本引入的标准库,内置了对异步IO的支持,asyncio的编程模型是一个消息循环,从asyncio模块中获取一个EventLoop的引用,然后把需要执行的协程放到EventLoop中执行就实现了异步IO。
import asyncio
@asyncio.coroutine
def method1():
print('method1_0')
r = yield from asyncio.sleep(1);
print('method1_1')
loop = asyncio.get_event_loop()
loop.run_until_complete(method1())
loop.close()
---
method1_0
method1_1
用@asyncio.coroutine把method1方法标记为一个coroutine类型,放到EventLoop中去执行,asyncio.sleep(1)是一个耗时1秒的操作,本身也是一个coroutine,在此期间,主线程并未等待,而是去执行EventLoop中其它可以执行的coroutine,当asyncio.sleep(1)返回时,拿到返回值继续执行下一行。
多个任务并行执行:
import asyncio
@asyncio.coroutine
def method1():
print('method1_0')
r = yield from asyncio.sleep(1);
print('method1_1')
@asyncio.coroutine
def method2():
print('method2_0')
r = yield from asyncio.sleep(1);
print('method2_1')
loop = asyncio.get_event_loop()
tasks = [method1(), method2()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
---
method2_0
method1_0
method2_1
method1_1
输出完method2_0后立即输出了method1_0,等了1秒后输出了method2_1;
用asyncio的网络连接来并行读取几个网站的header信息:
import asyncio
@asyncio.coroutine
def method1(host):
print('get %s' % host)
connection = asyncio.open_connection(host, 80)
reader, writer = yield from connection
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
writer.write(header.encode('utf-8'))
yield from writer.drain()
while True:
line = yield from reader.readline()
if line == b'\r\n':
break
print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
writer.close()
loop = asyncio.get_event_loop()
tasks = [method1(host) for host in ("www.163.com", "www.baidu.com", "www.sina.com.cn")]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
---
get www.sina.com.cn
get www.163.com
get www.baidu.com
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
...
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Server: nginx
www.sina.com.cn header > Date: Fri, 19 Jan 2018 05:36:04 GMT
www.sina.com.cn header > Content-Type: text/html
...
www.baidu.com header > HTTP/1.1 200 OK
www.baidu.com header > Date: Fri, 19 Jan 2018 05:36:04 GMT
www.baidu.com header > Content-Type: text/html
www.baidu.com header > Content-Length: 14613
www.baidu.com header > Last-Modified: Wed, 17 Jan 2018 03:40:00 GMT
...
三个连接由一个线程并发完成。
1.5python3.5版本后asyncio写法的改变
用asyncio提供的@asyncio.coroutine来把一个generator标记为一个coroutine类型,然后在coroutine内部通过yield from语句调用coroutine实现异步操作;为了简化并更好的标识异步IO,Python3.5版本开始修改了写法:
a.@asyncio.coroutine替换成async;
b.yield from 替换成await;
之前的一个例子就变成了下面这样:
import asyncio
async def method1():
print('method1_0')
r = await asyncio.sleep(1);
print('method1_1')
loop = asyncio.get_event_loop()
loop.run_until_complete(method1())
loop.close()
其它写法不变,只能在Python3.5及以上版本使用。
1.6aiohttp
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架,用到后台服务器上,可以用单线程+coroutine实现多用户的高并发支持。
pip install aiohttp
下面用aiohttp实现一个需求:
a.访问首页/返回
<h1>index</h1>
b.访问/hello/{name}则根据参数返回
<h1>hello {name}</h1>
.
import asyncio
from aiohttp import web
async def home(request):
await asyncio.sleep(0.5)
return web.Response(body=b'<h1>home</h1>', content_type='text/html')
async def hello(request):
await asyncio.sleep(0.5)
text = '<h1>param %s</h1>' % request.match_info['name']
return web.Response(body=text.encode('utf-8'), content_type='text/html')
async def init(loop):
app = web.Application(loop=loop)
app.router.add_route('GET', '/', home)
app.router.add_route('GET', '/hello/{name}', hello)
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
print('Server start at http://127.0.0.1:8000...')
return srv
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()