说明python的协程之前,先说明进程与线程的概念。
计算机运行在硬件之上的是操作系统,在操作系统上用户能够运行各种应用软件。为保证应用程序间独立运行,操作系统有一个专门控制管理他们的进程控制块。抽象的解释: 进程就是一个程序在一个数据集上的一个执行过程
。进程一般由程序,数据集和进程控制块组成。程序是由我们编写完成完成默写特定功能的代码;数据集则是程序运行过程中所需要的使用资源;进程块是描述进程的,包括进程的执行状态等,操作系统透过它对进程进行管控,操作系统就是通过它才能感知进程的存在。每个进程都有各自的一块独立的内存,保证进程彼此间的内存地址空间的隔离。
线程可以认为是轻量级的进程,它是程序执行的最小单元,由线程ID、程序计数器、寄存器集合 和堆栈共同组成。实际上在linux内核中,两者几乎没有差别,除了一点——线程并不产生新的地址空间和资源描述符表,而是复用父进程的。就是说线程没有自己的独立系统资源,而是使用其所在进程的系统资源。但是无论如何,线程的调度和进程一样,必须陷入内核态
线程是隶属于进程中的,一个进程中至少有一个进程。同一进程中的线程,共享相同的系统资源,并且在进程退出时,该进程所产生的线程均被强制退出清空。值得注意,在python中,由于有GIL(全局解释锁)的存在,所以多线程对于计算密集型的并发,是不会提高效率的,反倒是线程间彼此切换,增加开销。不过对于I/O密集型,还是有一定提升的。
协程又称微线程
也是为了不需要操作系统的介入完成程序在上下文间的切换,说白了就是函数间跳转切换。这个切换是我们人为在程序中通过yield控制的,并非多线程操作系统在线程间切换。
def iter(n):
for i in range(n):
yield i
if __name__ == '__main__':
n = 3
for i in iter(n):
print(i, end=',') # Output: 0, 1, 2
print()
c = iter(n)
while n:
i = c.send(None)
print(i, end=',') # Output: 0, 1, 2
n -=1
在这里 yield
的作用就是将当前的程序挂起, 回归主程序, 在左图例子中, 函数 iter
的执行将返回一个可迭代的生成器, 作用如下:
实现异步的核心
可以看出, 两段代码其实是等效的, 本质上就是迭代的内部调用了 生成器.send(None)
使得被挂起的函数得以被唤醒继续执行, 该函数等效于 next(生成器)
注意上文提到了两个函数。通过yield我们可以将返回值从生成器内部传向主函数,同样的,我们可以使用send将参数从主函数(或者说主线程)传给生成器,并唤醒生成器,该参数将作为yield的返回值并继续往下执行。直到下一个yield将生成器挂起,yield的值(或者说yield右侧的值)将作为主函数中send的返回值继续往下执行。直到触发StopIteration(说明生成器执行完毕)。
next与send(None)等效,并没有什么区别。
yield from用于重构生成器,比较简单的做法就是利用一个函数将一个生成器包起来。如下所示:
可以看出, copy_iter 对 iter 进行了封装, 实现了和 iter 同样的功能
如果不通过 yield form, 要实现同样的功能将会变得很复杂, 还得处理各种异常情况
def iter(n):
for i in range(n):
yield i
def copy_iter(n):
yield from iter(n)
if __name__ == '__main__':
n = 3
for i in copy_iter(n)
print(i, end=',') # Output: 0, 1, 2