首页 > 编程笔记 > Python笔记 阅读:1

Python协程的用法(附带实例)

众所周知,CPU 的运行速度远远快于硬盘、网络等 IO 操作的速度。在单进程、单线程程序中,一旦遇到文件读写、网络通信等 IO 操作,程序的其他代码就需要等待 IO 操作完成才能继续执行,这种情况被称为同步 IO。可见,纵使 CPU 执行代码的速度很快,在同步 IO 程序中,CPU 资源也是白白浪费的。

由于 IO 操作阻塞了当前线程,导致其他代码无法执行,因此采用多进程或者多线程来并发执行代码,当一个线程遇到 IO 操作被挂起时,其他线程将获得 CPU 资源而继续执行,通过多进程或多线程实现了多个任务,高效地利用了 CPU 的资源。

多进程和多线程的方式虽然解决了并发问题,但是随着多进程和多线程数量的不断增多,它们之间切换的资源消耗巨大。CPU 的运行时间大量消耗在切换上,用于执行代码的时间就减少了,同样浪费了计算机的资源。

为了实现并发和多个任务,多进程和多线程只是解决该问题的一种办法。Python 还提供了更优的解决方案,那就是协程和异步IO。

Python协程

协程(Coroutine)是可以协同运行的例程,类似于 CPU 的中断,可由用户调度,可在一个子程序内中断去执行其他的子程序。

这样解释起来可能不容易理解,可以使用实际代码来讲解,通过协程改写之前生产者和消费者的程序 queue_thread.py,编写程序代码,保存在文件 yield.py 中,即:
def Producer(c):
    c.send(None)
    n = 0
    while n < 5:
        n += 1
        print('Producer has created %s' % n)
        data = c.send(n)
        print('Consumer return; %s' % data)
    c.close()

def Consumer():
    data = ''
    count = 0
    while count < 6:
        count += 1
        content = yield data

        if not content:
            return
        print('Consumer has used %s' % content)
        data = 'done'

c = Consumer()
Producer(c)
运行结果为:

Producer has created 1
Consumer has used 1
Consumer return; done
Producer has created 2
Consumer has used 2
Consumer return; done
Producer has created 3
Consumer has used 3
Consumer return; done
Producer has created 4
Consumer has used 4
Consumer return; done
Producer has created 5
Consumer has used 5
Consumer return; done

接下来将逐步分析程序 yield.py 的代码:
通过协程的使用同样实现了生产者和消费者的模型,程序在一个线程中执行。

Python协程与多线程对比

上一节通过协程 yield.py 改写了生产者和消费者的程序,修改后的 queue_thread.py 代码为:
import threading, time
import queue
q = queue.Queue()

def Producer():
    n = 0
    while n < 1000:
        n += 1
        q.put(n)
        # print('Producer has created %s' % n)
        # time.sleep(0.1)

def Consumer():
    count = 0
    while count < 1000:
        count += 1
        data = q.get()
        # print('Consumer has used %s' % data)
        # time.sleep(0.2)

p = threading.Thread(target = Producer, name="")
c = threading.Thread(target = Consumer, name="")

修改后的 yield.py 代码为:
def Producer(c):
    c.send(None)
    n = 0
    while n < 1000:
        n += 1
        # print('Producer has created %s' % n)
        data = c.send(n)
        # print('Consumer return; %s' % data)
    c.close()

def Consumer():
    data = ''
    count = 0
    while count < 1000:
        count += 1
        content = yield data
        if not content:
            return
        # print('Consumer has used %s' % content)
        data = 'done'

c = Consumer()
Producer(c)

通过 Linux 自带的 time 命令测试以上两个程序的运行耗时,结果为:
# time -p python ./queue_thread.py
real 0.04
user 0.03
sys 0.00

# time -p python ./yield.py
real 0.02
user 0.02
sys 0.00
测试结果的含义如下:
可以看出,多线程代码是协程代码运行时间的两倍,将循环次数扩大到 100000 继续测试,结果为:
# time -p python ./queue_thread.py
real 0.72
user 0.68
sys 0.01

# time -p python ./yield.py
real 0.05
user 0.04
sys 0.00
从测试结果可以看出,随着循环次数的扩大,多线程效率与协程差距更加巨大。

与多线程相比,协程有如下优势:
由于协程在一个线程内执行,因此对于多核 CPU 平台,当并发量很大时,可以用多进程+协程的编程方式,既可充分利用多核 CPU,又可充分发挥协程的高效率,程序可以获得极高的性能。

相关文章