python 协程

1. 协程是什么?

协程,英文名Coroutine。它是一种能够让程序暂停执行并能够恢复执行的机制。可以将一个任务分为多个子任务,然后在任务之间来回切换,最终完成整个任务。协程可以让程序在执行io密集型任务时,不必等到io请求返回,而是可以先切换到其它任务,这样可以让CPU的利用率更高。

1.1 协程的特点

协程相对于线程和进程的特点:

不会发生线程/进程的上下文切换,更加轻量级;

不需要和操作系统直接打交道,可以自己控制任务的切换;

具有极高的执行效率和极好的扩展性。

2. Python 实现协程的方法

Python有很多实现协程的方法,其中比较典型的有:

生成器(generator);

使用 async 和 await 关键字;

使用第三方库 gevent。

2.1 生成器实现协程

在Python 2.x中,生成器还没有定义 coroutine 类型,但是在 3.x中,可以使用 coroutine 类型并定义一个__await__()方法。这个方法返回自身,也就是生成器对象。因此,可以使用生成器来实现协程。

def subgen():

while True:

x = yield

print(x)

def coroutine(func):

def start(*args, **kwargs):

cr = func(*args, **kwargs)

next(cr)

return cr

return start

@coroutine

def average():

count = 0

sum = 0

average = None

while True: # 子生成器

x = yield average

count += 1

sum += x

average = round(sum / count, 2)

avg = average()

subgen1 = subgen()

subgen2 = subgen()

next(avg)

subgen1.send(10)

subgen1.send(20)

subgen2.send(30)

print(avg.send(None))

print(avg.send(40))

打印输出如下:

None

15.0

25.0

35.0

在这个示例中,使用生成器定义了两个子生成器,即 subgen1 和 subgen2。然后,定义了一个协程 average,将其中包含的主逻辑简化为了维护一个总和 sum 和一个计数器 count,并求出平均值。下一步,应该调用 subgen1 和 subgen2,向其传递参数,并打印平均值。

2.2 async 和 await 实现协程

Python3.5 引入的关键字 async 和 await,它们被设计成更加简便的编写异步代码的方式,它们也是实现协程的另一种方式。

async定义的函数就是一个协程,await表明一个协程可以等待另一个协程执行完毕。

import time

async def task1():

print('Start task1')

await asyncio.sleep(1) # 模拟io操作

print('Finish task1')

async def task2():

print('Start task2')

await asyncio.sleep(0.5) # 模拟io操作

print('Finish task2')

async def main():

start_time = time.time()

await task1()

await task2()

end_time = time.time()

print(f'Time interval: {end_time - start_time:.2f} seconds')

asyncio.run(main())

运行结果如下,可以看到,task1 和 task2 几乎是同时执行的,而不是像多线程那样一个一个执行。

Start task1

Start task2

Finish task2

Finish task1

Time interval: 1.50 seconds

2.3 gevent 实现协程

使用第三方库 gevent 可以让Python应用程序轻松地实现协程,可以使用 gevent.spawn、gevent.joinall 等函数在程序中创建协程。

import gevent

import time

def task1():

print('Start task1')

gevent.sleep(1) # 因为 gevent 提供的是微线程(greenlet),因此这里是使用 gevent.sleep 进行协程切换,而不是 time.sleep

print('Finish task1')

def task2():

print('Start task2')

gevent.sleep(0.5)

print('Finish task2')

def main():

start_time = time.time()

gevent.joinall([gevent.spawn(task1), gevent.spawn(task2)]) # 传入协程对象列表,使用 joinall 进行等待

end_time = time.time()

print(f'Time interval: {end_time - start_time:.2f} seconds')

if __name__ == '__main__':

main()

运行结果与使用 async 和 await 的代码示例基本一样。

3. Python协程的应用场景

协程相对于多线程的最主要优点是有更高的执行效率,同时避免了多线程中的死锁、竞态条件等问题。因此,Python协程广泛应用于以下场景:

网络爬虫:由于网络请求的io操作非常密集,使用协程可以有效利用io等待的时间,提高数据采集效率;

Web框架:例如 Tornado 的协程异步处理方式可以让请求处理更加高效;

大量IO操作:能够避免由于IO等待导致的长时间等待问题。

结语

协程作为一种可以暂停执行并能够恢复执行的机制,可以让程序在执行io密集型任务时不必等到io请求返回,更好地利用CPU资源。Python提供了多种协程实现的方式,包括生成器、async/await 关键字、第三方库 gevent等。协程广泛应用于网络爬虫、Web框架、大量IO操作等场景。希望本文能够帮助你理解Python协程的基本概念和应用。

后端开发标签