18.异步IO

一.协程
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版本开始修改了写法:

[email protected]替换成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()

猜你喜欢

转载自blog.csdn.net/Aislli/article/details/81178549
18.