Python 提供一流的协程,具有“coroutine”类型和新的表达式,如“async def”和“await”。它提供了用于运行协程和开发异步程序的“asyncio”模块。
在本节中,我们将更深入地了解协程。
1. 什么是协程
协程是一个可以挂起和恢复的函数。它通常被定义为通用子程序。可以执行子程序,从一点开始,在另一点结束。然而,协程可以执行然后挂起,并在最终终止之前恢复多次。具体来说,协程可以控制它们暂停执行的确切时间。这可能涉及特定表达式的使用,例如 Python 中的“await”表达式,如 Python 生成器中的 yield 表达式。
协程可能因多种原因而暂停,例如执行另一个协程,例如等待另一个任务,或等待一些外部资源,如套接字连接或进程返回数据。
协程用于并发。可以同时创建和执行许多协程。它们可以控制何时挂起和恢复,从而允许它们在并发任务执行时进行合作。这称为协作式多任务处理,不同于通常与线程一起使用的多任务处理,称为抢占式多任务处理。抢占式多任务涉及操作系统选择暂停和恢复哪些线程以及何时这样做,而不是在协作多任务的情况下由任务自己决定。
现在我们对什么是协程有了一些了解,让我们通过将它们与其他熟悉的编程结构进行比较来加深这种理解。
2. 协程与例程和子例程
“例程”和“子例程”在现代编程中通常指的是同一事物。也许更准确地说,例程是程序,而子例程是程序中的函数。例程有子例程。它是一个离散的表达式模块,它被分配了一个名称,可以接受参数并可以返回一个值。
- 子例程:可按需执行的指令模块,通常已命名,可采用参数并返回值。也称为函数
一个子程序被执行,遍历表达式,并以某种方式返回。通常,一个子程序被另一个子程序调用。协程是子例程的扩展。这意味着子例程是一种特殊类型的协程。
协程在很多方面都像子例程,例如:
- 它们都是离散的命名表达式模块。
- 他们都可以接受争论,也可以不接受。
- 它们都可以返回一个值,也可以不返回。
主要的区别在于它在返回和退出之前选择了多次暂停和恢复执行。协程和子例程都可以调用自己的其他实例。一个子程序可以调用其他子程序。协程执行其他协程。但是,协程也可以执行其他子例程。当一个协程执行另一个协程时,它必须暂停执行并允许另一个协程在另一个协程完成后恢复。这就像一个子程序调用另一个子程序。不同之处在于协程的暂停可能允许任意数量的其他协程也运行。这使得调用另一个协程的协程比调用另一个子例程的子例程更强大。它是协同程序促进的协作多任务处理的核心。
3. 协程与生成器
生成器是一种可以暂停其执行的特殊函数。生成器函数可以像普通函数一样定义,尽管它在暂停执行并返回值时使用 yield 表达式。生成器函数将返回一个可以遍历的生成器迭代器对象,例如通过 for 循环。每次执行生成器时,它都会从上一次挂起的点运行到下一个 yield 语句。
协程可以使用“await”表达式挂起或屈服于另一个协程。一旦等待的协同程序完成,它将从这一点恢复。我们可能会将生成器视为循环中使用的一种特殊类型的协程和协作多任务处理。
在协程被开发之前,生成器被扩展,以便它们可以像 Python 程序中的协程一样使用。这需要大量的生成器技术知识和自定义任务调度程序的开发。这是通过更改生成器和引入“yield from”表达式实现的。
这些后来被弃用,取而代之的是现代的 async/await 表达式。
4. 协程与任务
子例程和协程可能代表程序中的“任务”。但是,在 Python 中,有一个称为 asyncio.Task 对象的特定对象。
协程可以包装在 asyncio.Task 对象中并独立执行,而不是直接在协程中执行。 Task 对象提供异步执行协程的句柄。
- Task:一个可以独立执行的包装协程。
这允许包装的协程在后台执行。调用协程可以继续执行指令而不是等待另一个协程。Task 不能单独存在,它必须包装一个协程。因此,Task 是协程,但协程不是任务。
5. 协程与线程
协程比线程更轻量级。
- Thread:与协程相比重量级
- Coroutine:与线程相比是轻量级的。
协程被定义为一个函数。线程是由底层操作系统创建和管理的对象,在 Python 中表示为 threading.Thread 对象。
- Thread:由操作系统管理,由 Python 对象表示。
这意味着协程通常可以更快地创建和开始执行并且占用更少的内存。反之,线程的创建和启动速度比协程慢,占用的内存也更多。协程在一个线程内执行,因此一个线程可以执行多个协程。
6. 协程与进程
协程比进程更轻量级。事实上,线程比进程更轻量级。进程是计算机程序。它可能有一个或多个线程。Python 进程实际上是 Python 解释器的一个单独实例。
进程与线程一样,由底层操作系统创建和管理,并由 multiprocessing.Process 对象表示。
- Process:由操作系统管理,由 Python 对象表示。
这意味着协程的创建和启动速度明显快于进程,并且占用的内存也少得多。协程只是一个特殊的函数,而进程是至少有一个线程的解释器实例。
7. 什么时候将协程添加到 Python
协程扩展了 Python 中的生成器。长期以来,生成器一直在慢慢地向一流的协程迁移。我们可以探索 Python 的一些主要变化以添加协程,我们可以将其视为概率添加 asyncio 的一个子集。像 send() 和 close() 这样的新方法被添加到生成器对象中,以允许它们更像协程。
第二种基于生成器的协程方法被添加到 Python 3.4 作为 Python 生成器的扩展。协程被定义为使用 @asyncio.coroutine 装饰器的函数。协程是通过 asyncio 模块使用 asyncio 事件循环执行的。协程可以通过“yield from”表达式挂起并执行另一个协程
# define a custom coroutine in Python 3.4
@asyncio.coroutine
def custom_coro():
# suspend and execute another coroutine
yield from asyncio.sleep(1)
“yield from”表达式仍然可用于生成器,尽管它是一种在协程中暂停执行的弃用方法,有利于“await”表达式。
我们可以说协程是在 Python 3.5 版本中作为一等对象添加的。这包括对 Python 语言的更改,例如“async def”、“await”、“async with”和“async for”表达式,以及协程类型。