python异步I/O

一,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) 之间的调用关系。

  1. generator 启动之后会阻塞在yield r这里等待获取返回值n (执行从consumer转到produce)
  2. 当调用了c.send(n) 之后,yield就会获取到n继续执行 (执行从produce转到consumer)
  3. 当再次执行到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。简化书写。

需要注意,

  1. async只能在def with for之前使用,把它们编程协程。
  2. 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应用

先来看看服务器对待请求的策略:

同步服务器:

  1. 服务器只有一个进程和一个线程,每个请求都顺序处理
  2.  多进程:来一个请求就开起一个新进程去处理 稳定 开销大
  3. 多线程:来一个请求就开起一个新线程去处理 开销小 不稳定

异步服务器:

  1. 单线程+协程 稳定 开销小

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()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值