异步编程

目前为止,我们在做的都是同步编程。同步编程执行过程很简单:一个程序从第一行开始,逐行执行一直到末尾。每次调用一个函数时,程序就会等待这个函数返回然后在执行下一行。

在异步编程中,函数地执行通常是非阻塞的。换句话说,每次你调用一个函数它就会立即返回,但相对得,这就表示函数并不会立即被执行。它有了一种机制(名为 调度程序),让可以随时在未来执行这些函数。

使用异步编程会导致程序在任何异步函数开始之前就有可能结束掉。通常的解决方法是让异步函数返回“future(未来任务)”或者“promises(预先任务)”。让其标识出这是一个异步函数。最终,由有调度程序的异步编程框架阻塞或者说等待这些异步函数完成它们的“future(未来任务)”。

自Python 3.6开始,“asyncio”模块与asyncawait关键字相结合,来让我们写多任务协作程序。在此类编程中,当一个协同函数在开小差或者等待输入时,都会由yield交出控制权给另一个协同函数。

思考一下下面这个异步函数,它的作用是返回一个数字的平方,并且在返回前会睡眠一秒钟。 异步函数由async def声明。现在先忽略其中的await关键字:

import asyncio

async def square(x):
    print('Square', x)
    await asyncio.sleep(1)
    print('End square', x)
    return x * x

# 创建一个事件循环。
loop = asyncio.get_event_loop()

# 执行异步函数并且等待其完成。
results = loop.run_until_complete(square(1))
print(results)

# 将事件循环关闭。
loop.close()

事件循环(https://docs.python.org/3/library/asyncio-eventloop.html )是在有很多事物时,Python可以使用调度机制来执行这些异步函数的一种方式。我们使用循环来让这些函数运行直到完成。这是一种同步机制,目的是让我们得到任何结果前(await asyncio.sleep(1)),剩下的打印语句都不会被执行。 。

之前的例子并不能很好地说明异步编程,因为我们没有写得很复杂,而且也只执行了一次函数。不过你可以想一下,如果你要执行square(x)3次呢:

square(1)
square(2)
square(3)

因为square()里面有一个睡眠函数,总执行时间差不多要3秒钟。但每次执行一个函数时计算机就会陷入呆滞,在那一秒钟里什么也不做,我们为什么不能让它在之前的那个函数睡眠时来执行下一个呢?我们稍加改动,把它变成异步:

# 执行异步函数直到其完成。
results = loop.run_until_complete(asyncio.gather(
    square(1),
    square(2),
    square(3)
))
print(results)

一般来说,我们使用asyncio.gather(*tasks)来让循环等待所有的任务完成。因为协同程序几乎会在同一时间内启动,整个程序只需要1秒钟即可完成。要注意,**asyncio.gather()**不会按顺序执行协同函数,尽管它会返回一个按顺序排列的结果列表。

$ python3 python_async.py
Square 2
Square 1
Square 3
End square 2
End square 1
End square 3
[1, 4, 9]

有时我们需要立即处理所返回的结果。对于这种情况,我们可以使用两个协同函数,让第二个来处理结果,使用asyncio.as_completed()就可以:

(...)

async def when_done(tasks):
    for res in asyncio.as_completed(tasks):
        print('Result:', await res)

loop = asyncio.get_event_loop()
loop.run_until_complete(when_done([
    square(1),
    square(2),
    square(3)
]))

打印出的东西差不多是这样的:

Square 2
Square 3
Square 1
End square 3
Result: 9
End square 1
Result: 1
End square 2
Result: 4

最后要说的是,异步函数中可以使用await关键字来调用另一个异步函数。

async def compute_square(x):
    await asyncio.sleep(1)
    return x * x

async def square(x):
    print('Square', x)
    res = await compute_square(x)
    print('End square', x)
    return res

异步编程部分练习

  1. 写一个异步协同函数,它接受两个参数,并将它们相加,并且之后会睡眠这些秒。请使用异步循环来调用它。
  2. 修改一下之前的那个程序,让它调用两次。

书籍推荐