协程,又称微线程
协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)
Python中的协程大概经历了如下三个阶段:
1、最初的生成器变形yield/send
2、yield from
3、在最近的Python3.5版本中引入async/await关键字
协程主要是通过运行代码中的时间延迟来完成任务,我们可以这样理解线程、进程、协程:
一条流水线上有很多工人进行生产工作,线程就是增加工人进行工作,进程就是添加流水线进行工作,协程就是一个流水线上的一个工人把他的工作做好了,然后进行其他的工作。
学习协程我们需要先了解下面几个概念:
同步、异步
同步:是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式
异步:是指代码调用IO操作时,不必等IO操作完成就返回的调用方式
阻塞、非阻塞
阻塞:从调用者的角度出发,如果在调用的时候,被卡住,不能再继续向下运行,需要等待,就说是阻塞
非阻塞:从调用者的角度出发, 如果在调用的时候,没有被卡住,能够继续向下运行,无需等待,就说是非阻塞
生成器-send方法
send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值
def create_num(num):
a, b = 0, 1
current_num = 0
while current_num < num:
result = yield a
print("result-->", result)
a, b = b, a+b
current_num += 1
g = create_num(5)
print(next(g))
# 关闭生成器
# g.close()
# 如果前面不调用next 第一次必须发送None
print(g.send('haha'))
如上述代码的create_num是一个生成斐波那契数列的生成器,在这个代码中使用了send方法,第一个打印的内容时print(next(g))打印的0,第二个打印的内容是print(“result–>”, result)打印的result–>haha,第三个打印的内容为print(g.send(‘haha’))打印的1.
第一个打印出的0大家应该都能够理解,第二个打印出的值是因为send方法给yield a send了一个’haha’,所以result为’haha’,而g.send(‘haha’)取得还是生成器yield出得第二个值1。
其中如果第一次没有print(next(g)),直接使用send方法,则只能send(None)。
使用yield完成多任务
import time
def task1():
while True:
print("--1--")
time.sleep(0.1)
yield
def task2():
while True:
print("--2--")
time.sleep(0.1)
yield
def main():
t1 = task1()
t2 = task2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
yield from介绍
在python3.3新加了yield from语法
lis = [1, 2, 3]
def generator_1(lis):
yield lis
def generator_2(lis):
yield from lis
if __name__ == '__main__':
for i in generator_1(lis):
print(i)
for i in generator_2(lis):
print(1)
上述代码的运行结果如下
generator_1的运行结果为一个列表,而generator_2的运行结果为列表中的每一个值,我们可以暂时粗略的将yield from理解为如果yield的对象是一个可迭代的,那就将其迭代一遍。
yield 案例:
"""子生成器"""
def generator_1():
total = 0
while True:
x = yield
print('加', x)
if not x: # if not None: True
break
total += x
return total
"""委托生成器"""
def generator_2():
while True:
# 建立调用方和子生成器的通道
total = yield from generator_1() # 子生成器
print('加和总数是:', total)
"""调用方"""
def main():
# g1 = generator_1()
# g1.send(None)
# g1.send(2)
# g1.send(3)
# g1.send(None)
g2 = generator_2()
g2.send(None)
g2.send(2)
g2.send(3)
g2.send(None)
if __name__ == '__main__':
main()
子生成器:yield from后的generator_1()生成器函数是子生成器
委托生成器:generator_2()是程序中的委托生成器,它负责委托子生成器完成具体任务。
调用方:main()是程序中的调用方,负责调用委托生成器。
使用greenlet完成多任务
from greenlet import greenlet
import time
# 协程利用程序的IO 来切换任务
def sing():
while True:
print('I\'m singing')
gr2.switch()
time.sleep(0.5)
def dance():
while True:
print('I\'m dancing')
gr1.switch()
time.sleep(0.5)
if __name__ == '__main__':
gr1 = greenlet(sing)
gr2 = greenlet(dance)
gr1.switch()
使用greenlet完成多任务需要在一个任务的运行中设置另一个任务的开始,即在sing中的gr2.switch()。
使用gevent完成多任务
import gevent
import time
# 协程利用程序的IO 来切换任务
def sing(n):
for i in range(n):
print('I\'m singing...%s' % i)
time.sleep(0.5)
def dance(n):
for i in range(n):
print('I\'m dancing...%s' % i)
time.sleep(0.5)
if __name__ == '__main__':
g1 = gevent.spawn(sing, 5)
g2 = gevent.spawn(dance, 5)
g1.join()
g2.join()
我们运行上述代码发现并没有实现多任务,而是sing运行结束了再开始的dance的运行。
这是因为在gevent中识别不了time的时间延迟,此时应该使用gevent中的时间延迟gevent.sleep().
import gevent
# 协程利用程序的IO 来切换任务
def sing(n):
for i in range(n):
print('I\'m singing...%s' % i)
gevent.sleep(0.5)
def dance(n):
for i in range(n):
print('I\'m dancing...%s' % i)
gevent.sleep(0.5)
if __name__ == '__main__':
g1 = gevent.spawn(sing, 5)
g2 = gevent.spawn(dance, 5)
g1.join()
g2.join()
或者可以在gevent中导入monkey,将代码改写如下:
import gevent
import time
from gevent import monkey
monkey.patch_all()
# 协程利用程序的IO 来切换任务
def sing(n):
for i in range(n):
print('I\'m singing...%s' % i)
time.sleep(0.5)
def dance(n):
for i in range(n):
print('I\'m dancing...%s' % i)
time.sleep(0.5)
if __name__ == '__main__':
g1 = gevent.spawn(sing, 5)
g2 = gevent.spawn(dance, 5)
g1.join()
g2.join()
monkey.patch_all()实现了将程序中用到的耗时操作 换为gevent中实现的功能。
同时也可将上述代码改写为joinall的形式:
import gevent
import time
from gevent import monkey
monkey.patch_all()
# 协程利用程序的IO 来切换任务
def sing(n):
for i in range(n):
print('I\'m singing...%s' % i)
time.sleep(0.5)
def dance(n):
for i in range(n):
print('I\'m dancing...%s' % i)
time.sleep(0.5)
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(sing, 5),
gevent.spawn(dance, 5)
])
线程进程协程简单总结
进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很最大,效率很低
线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
协程切换任务资源很小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发