Python--异步编程

同步和异步

  • 同步:调用一个函数,返回结果是自己去获取的,不管是阻塞还是非阻塞
  • 异步:调用一个函数,返回结果是别人通过通知、回调等方式给你的

在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。多线程和多进程虽然解决了并发问题,但是系统不能无上限的增加线程,由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,会导致性能下降。

为了解决CPU和IO设备的速度严重不匹配的问题,多线程和多进程只是解决这问题的一种方法。另一种方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了,一段时间后,当IO返回结果时,再通知CPU进行处理。

协程

了解异步IO之前,我们先来了解协程,协程,又称微线程。

Python对协程的支持是通过生成器实现的,在生成器中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切回到生产者继续生产,效率极高

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('Producing %s...' % n)
        r = c.send(n)
        print('Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

输出:

Producing 1...
Consuming 1...
Consumer return: 200 OK
Producing 2...
Consuming 2...
Consumer return: 200 OK
Producing 3...
Consuming 3...
Consumer return: 200 OK
Producing 4...
Consuming 4...
Consumer return: 200 OK
Producing 5...
Consuming 5...
Consumer return: 200 OK

注意到consumer函数是一个生成器,把consumer传入produce后:

  • 首先调用c.send(None)启动生成器;
  • 然后,一旦生产了东西,通过c.send(n)切换到comsumer执行;
  • consumer通过yield拿到消息,处理,又通过yield把结果传回;
  • produce拿到consumer处理的结果,继续生产下一条消息;
  • produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为协程,而非线程的抢占式多任务。

异步IO

asyncio是Python3.4版本引入的标准库,直接内置了对异步IO的支持。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

import threading
import asyncio

@asyncio.coroutine
def hello():
    print('Hello world! (%s)' % threading.currentThread())
    yield from asyncio.sleep(1)
    print('Hello again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

@asyncio.coroutine就是把hello这个生成器标记为协程,然后,我们就把这个协程扔到事件循环中执行。

输出:

Hello world! (<_MainThread(MainThread, started 11180)>)
Hello world! (<_MainThread(MainThread, started 11180)>)
(暂停约1秒)
Hello again! (<_MainThread(MainThread, started 11180)>)
Hello again! (<_MainThread(MainThread, started 11180)>)

由输出信息可以看出,两个协程是由同一个线程并发执行的,如果把asyncio.sleep()换成真正的IO操作,则多个协程就可以由一个线程并发执行。

async/await

用asyncio提供的asyncio.coroutine可以把一个生成器标记为协程类型,然后在协程内部用yield from调用另外一个协程,实现异步操作。

为了简化并更好的标识异步IO,从Python3.5开始引入了新的语法async和await。

async和await是针对协程的新语法,要使用新的语法,只需要做两步简单的替换:

  • 把@asyncio.coroutine替换为async;
  • 把yield from替换为await.

用新语法重写上面的列子:

async def hello():
    print('Hello world! (%s)' % threading.currentThread())
    await asyncio.sleep(1)
    print('Hello again! (%s)' % threading.currentThread())

其他的代码保持不变

aiohttp

asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大,如果把asyncio用在服务端,例如Web服务器,由于HTTP连接就是IO操作,因此可以使用单线程+协程实现多用户的高并发支持。

asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架

客户端举例:

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'http://python.org')
        print(html)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

服务端举例:

from aiohttp import web

async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)

app = web.Application()
app.add_routes([web.get('/', handle),
                web.get('/{name}', handle)])

web.run_app(app)

你可能感兴趣的:(Python,Python,异步)