执行过程
- 普通函数:顺序执行,从函数开头执行到结尾,调用者等待函数执行完毕返回结果后才继续执行后续代码。函数执行过程中,遇到函数调用就暂停当前函数执行,转去执行被调用函数,被调用函数执行完返回结果,再继续执行原函数。例如:
def func1():
print("开始执行func1")
result = func2()
print("func1继续执行,func2返回结果:", result)
return "func1执行完毕"
def func2():
print("开始执行func2")
return "func2执行完毕"
func1()
- 协程:可以暂停执行并将执行权交回给调用者,之后又能从暂停的地方继续执行。协程使用
yield
关键字暂停执行并返回值,使用 send
方法向暂停的协程传入值并恢复执行。例如:
def simple_coroutine():
print('-> 协程启动')
x = yield
print('-> 协程接收值:', x)
my_coro = simple_coroutine()
next(my_coro)
my_coro.send(42)
资源占用
- 普通函数:在执行过程中,会一直占用栈空间等资源,直到函数执行完毕释放资源。如果函数调用层级过深,容易导致栈溢出。例如,一个递归函数如果没有正确的终止条件,不断调用自身,栈空间会不断被占用,最终导致栈溢出错误。
def infinite_recursion():
infinite_recursion()
- 协程:不需要像普通函数那样维护完整的调用栈,在暂停时可以释放部分资源,所以在资源占用上更加轻量级。多个协程可以在一个线程内交替执行,避免了线程切换带来的开销。
调用方式
- 普通函数:通过函数名加括号,传递参数的方式调用,调用后程序控制权转移到被调用函数,直到被调用函数返回,控制权才回到调用处。例如:
result = func(a, b)
,func
是普通函数,a
和 b
是参数。
- 协程:需要先创建协程对象,通常使用
next()
函数或者 send(None)
启动协程,使其运行到第一个 yield
处暂停。之后可以使用 send()
方法向协程传递数据并使其从暂停处继续执行。例如:
def coroutine():
value = yield
return value
my_coro = coroutine()
next(my_coro)
result = my_coro.send(10)