likes
comments
collection
share

tornado 并发编程系列(2)——asyncio和concurrent.futures

作者站长头像
站长
· 阅读数 12

Asyncio: Python的异步I/O库

asyncio 模块是 Python 3.4 版本中添加的一些强大且易于使用的异步编程工具。 为了有效地使用 asyncio 模块,首先要了解什么是协程,以及如何使用协程作为异步编程的基本单元。

协程与异步编程

协程是异步编程的基本单位,是一种轻量级的线程(占用的资源更少,支持更高的并发度),不同于一般的线程,它不会被操作系统或虚拟机所调度,而是完全由应用程序自己控制。换言之,一个协程可以在一个线程中运行,并通过切换上下文的方式实现不同协程的轮流执行。

Python 的最新版本中引入了 asyncio 库,该库可以与协程一起使用,使得异步编程的实现更为便捷。它提供了丰富的工具,可以让程序员用最少的复杂代码来处理异步IO,包括混合同步和异步IO,以及其他相关任务。

async / await

在Python 3.5中,协程引入了新语法async/await,任务间的切换变得更加直观自然,并与 Python 的异步I/O库(asynico)高度结合。

在async和await的帮助下,使用异步操作的代码块从回调机制转向同步写法。 async用于定义一个协程,而 await用于中断正在运行的异步操作,直到结果被返回。例如:

import asyncio

async def foo():
    print('start...')
    await asyncio.sleep(2.0)
    print('end...')
 
async def bar():
    for i in range(3):
        await asyncio.sleep(0.5)
        print('bar...')
        await foo()

asyncio.run(bar())

在这个例子中,foo和bar都是coroutine对象,通过run_until_complete()run()隐式地将它们注册为事件循环,并运行它们。

这里的关键是使用await,它会挂起协程的状态,直到另一个协程生成的异步操作完成。在此示例中,foo()中的异步操作是等待2.0秒的时间延迟,它可以通过await asyncio.sleep(2.0)调用来生成。

最终,bar的代码片段内循环完成3次,并执行foo(),请注意,end消息是在foo()中的异步操作完成后才打印出来的。

asyncio.Future

asyncio.Future类是Python3.5库asyncio中的非常有用的组件,它类似于threading中的Future,但Future是协程上下文中使用的异步版本

一个Future是异步可调用对象,它表示一种可能不确定的操作,并且在以后可能返回结果。举个例子:一个Future可以表示“下载远程文件”,该操作随时可能完成(从现在开始到将来某个时间点),并且可以通过将回调函数附加到该Future来等待操作完成并处理结果。

import asyncio

async def my_coroutine():
    await asyncio.sleep(1)
    return 42

async def main():
    coro = my_coroutine()
    print(coro)  # <coroutine object my_coroutine at 0x7fd39c28b7c0>
    future = asyncio.ensure_future(coro)
    print(future)  # <Future pending>
    await future
    print(future)  # <Future finished result=42>
    
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.close()

在这个例子中,将协程实例对象传递给asyncio.ensure_future(),以生成一个表示该单个协程的Future。然后可以使用单独的协程在await表达式中调用它,并将返回值传递给future.result()。

Futures

Future与asyncio.Future类似,但是是Python3.2中内置的模块。它提供了一种简单的方法来处理需要长时间运行的操作,在等待操作完成时可以进行其他任务。

在concurrent.futures模块中,Future()是由Executor.submit()方法返回的对象,该方法将执行的函数及其输入参数传递到线程池中。

from concurrent.futures import ThreadPoolExecutor

# 定义一个任务
def expensive_function(x, y):
    ...
    return result

# 使用线程池并行运行函数,并返回结果
with ThreadPoolExecutor(max_workers=4) as ex:
    future = ex.submit(expensive_function, 1, 2)
    result = future.result()
    print(result)

在这个例子中,一个函数expensive_function()被定义为一个计算开销高昂的任务。ThreadPoolExecutor类提供了一个线程池并行运行函数的方法,最后一个future.result()函数会阻塞,直到结果计算完成并返回结果。

另外,futures.wait()返回一个包含Future对象的元组,这些Future对象未完成。可以将其用作已完成的所有任务的迭代器。

from concurrent.futures import ThreadPoolExecutor, wait

def expensive_function(x):
    ...
    return result

with ThreadPoolExecutor(max_workers=4) as ex:
    futures = {ex.submit(expensive_function, i): i for i in range(10)}
    done, not_done = wait(futures)

在此示例中,expensive_function()函数被调用10次,submit()方法返回的对象被添加到一个字典中,其中key是Future对象,value是这个Future对应的参数。wait()函数返回一个2元素元组,第一个元素包含已完成的Futures,第二个元素包含未完成的Futures。

总结

在本文中,我们介绍了两个函数库:asyncio和concurrent.futures。asyncio通过在异步应用程序中使用协程和async/await语法提供了丰富的组件。当然,在使用asyncio时要比在使用concurrent.futures时更具挑战性。同时,concurrent.futures提供更为简单的同步代码,它允许在代理方案中包含任何类型的耗时操作。

在许多情况下,异步编程可以显着提高程序的性能,也可以提供更好的用户体验。在继续使用程序的同步部分的同时,使用asyncio和concurrent.futures的最佳方法是找到适合的场景和任务,并根据需要使用这两个库来实现最佳的性能和用户体验。