您的位置:首页 > 编程语言 > Python开发

Python下异步IO和协程简介

2015-10-19 19:48 459 查看
1、协程(coroutine)

协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用。一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。

简而言之:协程是一种用户态的轻量级线程

假设A、B是同一线程下的两个协程,则这两个协程之间可以任意的切换,最重要的是解决了线程切换的阻塞问题(这也是为什么协程和异步IO通常在一起被提到)。协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。另外,由于不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

2、异步IO(asyncio)

在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。

在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。

因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。

多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。

由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。

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

3、代码举例

# -*- coding:utf-8 -*-

__auth__ = 'peic'

'''
Python 协程和异步IO
yield 、yield from的使用
asyncio 模块的使用
'''

# -*- 协程 -*-
# 首先需要理解yield
# 函数中有yield语句而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行,类似CPU的中断处理

def consumer():
    c_r = ''
    while True:
        # 此处yield接受调用者发出的参数,通过send进行调用
        # c.send(p_n)中n的值通过yield返回,赋值给r_n
        # send(value):The value argument becomes the result of the current yield expression.
        # consumer通过yield拿到消息,处理,又通过yield把结果传回。拿到的值是send传递的值,赋给c_n,返回的值是c_r
        c_n = yield c_r
        if not c_n:
            return
        print('[CONSUMER] Consuming %s...' % c_n)
        c_r = '200 OK'

def produce(c):
    c.send(None)
    p_n = 0
    while p_n < 5:
        p_n = p_n + 1
        print('[PRODUCER] Producing %s...' % p_n)

        # 调用send的generator(此处就是c),send语句会将参数(也就是p_n)的值传给这个生成器目前yield表达式的值(c_n)
        # 而send表达式的值(也就是传给p_r的值)会是generator的下一个值(next(c_r))
        # 通过调用返回的形式,c.send()就完成了函数的调用返回,即执行了一步generator(consumer)然后返回到原函数(produce),在这个过程中,yield起到中断返回作用
        p_r = c.send(p_n)
        print('[PRODUCER] Consumer return: %s' % p_r)
    c.close()

c = consumer()
produce(c)

# -*- 异步IO -*-
import asyncio
import threading

# @asyncio.coroutine把一个generator标记为coroutine类型
@asyncio.coroutine
def sub():
    print('sub start: ...')
    n = 10
    while True:
        print('yield start')
        # asyncio.sleep()也是一个coroutine类型的generator,所以线程不会中断,而是直接执行下一个循环,等待yield from的返回
        # 可以简单的理解为出现yield之后则开启一个协程(类似开启一个新线程),不管这个协程是否执行完毕,继续下一个循环
        # 开启新协程后,print('yield start')会因为继续执行循环被立即执行,可以通过打印结果观察
        r = yield from asyncio.sleep(1)
        n = n - 1
        print('---sub: %s,  thread:%s' %(n, threading.currentThread()))
        if n == 0:
            break

@asyncio.coroutine
def add():
    print('add start: ...')
    n = 10
    while True:
        print('yield start')
        r = yield from asyncio.sleep(2)
        n = n + 1
        print('+++add: %s,  thread:%s' %(n, threading.currentThread()))
        if n > 20:
            break

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [add(),sub()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()


输出结果:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
add start: ...
sub start: ...
---sub: 9,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 11,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 8,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 7,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 12,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 6,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 5,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 13,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 4,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 3,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 14,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 2,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 1,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 15,  thread:<_MainThread(MainThread, started 140436577416960)>
---sub: 0,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 16,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 17,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 18,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 19,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 20,  thread:<_MainThread(MainThread, started 140436577416960)>
+++add: 21,  thread:<_MainThread(MainThread, started 140436577416960)>


从结果可以看出,两个协程在同一线程中
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: