协程
协程概念:
协程: 又称微线程,纤程,协程是一种用户态的轻量级线程。
协程的作用: 在执行A函数的时候,可以随时中断,去执行B函数,然后中断继续执行A函数(可以自动切换),协程过程并不是函数调用(没有调用语句),过程很像多线程,然而协程只有一个线程在执行。
特点:
- 必须在只有一个单线程中实现并发
- 用户可以在自己的程序中保存多个控制流的的上下文栈
- 修改共享数据并不需要加锁
- 一个协程在遇到IO操作时自动切换到其他协程中
- 线程是CPU控制的,而协程是程序自身控制的。
协程原理:
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
**优点 **
- 无需原子操作的锁定和开销
- 方便切换控制流,简化编程模型
- 方便实现并发任务,可扩展性强,成本低
缺点:
无法利用多核资源:
协程的本质是个单线程,它不能同时将使用单个CPU 的多个核,协程需要和进程配合才能运行在多CPU上。
当进行阻塞操作时,会阻塞整个程序。
协程的实现:
1、yield、send
方案1协程通过yield和send来进行实现,具体实例:
def A():
print("start")
value = 0
while True:
receive = yield value
if receive == "quit":
break
print("A:::get a num is ::%d" % receive)
value = receive * 10
def B():
g = A()
g.send(None) # 启动
l = [i for i in range(3)]
len_l = len(l)
i = 0
while len_l > 0:
value = g.send(l[i])
print("B:::get a num %d" % value)
len_l -= 1
i += 1
try:
g.send("quit")
except:
print("结束协程,end")
B()
################################################################################
"""结果展示:"""
#start
#A:::get a num is ::0
#B:::get a num 0
#A:::get a num is ::1
#B:::get a num 10
#A:::get a num is ::2
#B:::get a num 20
#结束协程,end
在上述实例中,A() 中 receive = yield value 语句执行顺序主要是:
- 1、向函数外部抛出value
- 2、暂停执行,等待next()或者send()函数唤醒
- 3、被send()唤醒后,将send()发送数据赋值给receive
在启动时,先以send(None)启动生成器函数,如果以其他值启动会报错,之后执行的顺序实际为”3-1-2“循环执行,实例中B先向send()一条数据,A接受到数据后进行乘10处理,之后将结果返回给B,暂停等待B再次唤醒。
2、yield from
t = range(3)
def f1():
yield t
def f2():
yield from t
f1=f1()
f2=f2()
for _ in f1:
print("f1::",_)
for _ in f2:
print("f2::",_)
#########################
"""结果"""
#f1:: range(0, 3)
#f2:: 0
#f2:: 1
#f2:: 2
这里的yield from相当于实现了一次for循环,yield from之后必须接的是生成器或者迭代器,同时yield from也可以打开双通道实现协程操作
现在使用yield from代替for循环,yield from后面必须跟iterable对象(可以是生成器,迭代器)
3、asyncio.coroutine和yield from
@asyncio.coroutine 与 yield from结合实现协程
我们先看一下没用使用协程的运行状况:
def task1(n):
print("task1开始工作")
time.sleep(n)
print("task1工作结束")
def task2(n):
print("task2开始工作")
time.sleep(n)
print("task2工作结束")
start=time.time()
task1(2)
task2(4)
end=time.time()
print("任务总需要的时间:",end-start)
############################################
"""实验结果:"""
#task1开始工作
#task1工作结束
#task2开始工作
#task2工作结束
#任务总需要的时间: 6.002887725830078
当使用@asyncio.coroutine与yield from时
@asyncio.coroutine # 标志协程的装饰器
def task1_asyn(n):
print("task1开始工作")
yield from asyncio.sleep(n) # 这里一般为需要耗费大量时间的操作
print("task1工作结束")
@asyncio.coroutine
def task2_asyn(n):
print("task2开始工作")
yield from asyncio.sleep(n)
print("task2工作结束")
start=time.time()
loop=asyncio.get_event_loop() # 获取一个循环对象loop
tasks=[task1_asyn(2), task2_asyn(4)]
loop.run_until_complete(asyncio.wait(tasks)) # 完成事件循环,直到最后一个任务结束
loop.close()
end=time.time()
print("任务总需要的时间:",end-start)
###########################################
#task2开始工作
#task1开始工作
#task1工作结束
#task2工作结束
#任务总需要的时间: 4.004240274429321
我们可以明显看到整个任务所需要的时间比之前少了好多
4、async和await
他们可以理解成asyncio.coroutine/yield from的完美实现。当然,从Python设计的角度来说,async/await让协程表面上独立于生成器而存在,将细节都隐藏于asyncio模块之下,语法更清晰明了。加入新的关键字 async ,可以将任何一个普通函数变成协程。
async def mygen(alist):
while len(alist)>0:
index = random.randint(0, len(alist)-1)
print(alist.pop(index))
alist=['java', 'python', 'C++', 'C']
print(mygen(alist))
###################################
"""结果"""
#<coroutine object mygen at 0x000001E1CF3E4A48>
配合await实现协程
async def mygen(alist):
while len(alist)>0:
index = random.randint(0, len(alist)-1)
print(alist.pop(index))
await asyncio.sleep(0.2)
t1=['java', 'python', 'C++', 'C']
t2=[1,2,3,4,5,6,7,8]
tasks=[mygen(t1),mygen(t2)]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
###################################################
"""测试结果:"""
#3
#java
#5
#python
#4
#C
#7
#C++
#2
#8
#1
#6
我们可以看到,这里没有调用多线程,但是协程实现了两个任务的交替进行,实现异步非阻塞方式