10 协程2
10.1 协程简介
协程介绍
- 协程,又称为微线程,它是实现多任务的另一种方法,只不过比线程更小的执行单元,因为它自带CPU的上下文,这样只要在合适的时机,我们就要=可以把一个协程切换到另一个协程。
CPU上下文(CPU寄存器和程序计数器):
- CPU寄存器就是CPU的内置的容量小,但速度极快的内存。
- 程序计数器则是用来存储CPU在在执行的指令位置,或者即将执行的下一条指令位置。
协程与线程差异
- 线程:每个线程都有自己缓存Cache 等等数据,操作系统还会做这些数据的恢复操作,所以线程的切换非常消耗性能。
- 协程:单纯的操作CPU的上下文,所以一秒切换上百万次系统都能抗住。所以完成多任务的效率比线程和进程都高。
10.2 yield 实现协程
- 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()
生成器扩展
- next(g) 预激活
- g.send(None) 预激活
- g.send('需发送的值‘)激活yield 并且发送值
- 注意:此前必须有激活也就是next(g) 或g.send(None)
- 生成器函数的返回值在在异常中
def create_num(num):
a,b=0,1
current_num=0
while current_num <num:
res=yield a # 1.yield a 2.赋值
print(res)
a,b=b,a+b
current_num+=1
return 'hello world'
g=create_num(5)
10.3 yield from
作用
- 1.替代产生出值得嵌套 for 循环
- 2.yield from 的主要功能是打开双向通道,把最外层的调用方向和最内层的子生成器连接起来。因为yield from 的异常捕获更为完善。
替代产出值得嵌套 for 循环
from itertools import chain
lis=[1,2,3]
dic={
'name':'amy',
'age':18
}
for v in chain(lis,dic):
print(v)
# 参数:可迭代的对象-->传入多个 会打包为元组
# 返回值:chain 对象 所有,可以强转列表 显示 或者 for循环
# 本质:调用 __next__方法
# print(list(chain(lis,dic)))
'''
自己封装函数 实现与chain 一样的功能
'''
from itertools import chain
lis=[1,2,3]
dic={
'name':'amy',
'age':18
}
def my_chain(*args):
# print(args) 打包为元组
for my_iterable in args:
# 使用yield生成器实现
# for v in my_iterable:
# # print(v)
# yield v
# yield from 替代了 for 循环
yield from my_iterable
# my_chain(lis,dic)
# print(mc)
# for value in my_chain(lis,dic):
# print(value)
#
def ge_1(lis):
yield lis
def ge_2(lis):
yield from lis
for i in ge_1(lis):
print(i)
for i in ge_2(lis):
print(i)
10.4 greenlet实现协程
greenlent安装
- pip install greenlent
greenlet实现
from greenlet import greenlet
import time
def demo1():
while True:
print('demo1')
gr2.switch()
time.sleep(0.5)
def demo2():
while True:
print('demo2')
gr1.switch() # 切换到gr2 运行
time.sleep(0.5)
if __name__=='__main__':
gr1=greenlet(demo1)
gr2=greenlet(demo2)
gr1.switch() # 切换到gr1 运行
print('主函数')
# 协程 主要利用程程序 IO 来切换 也就是说堵塞的时间 来切换任务
10.5 gevent实现协程
gevent介绍
- greenlet 已经实现了协程,但是 这个还的仍共切换,就很麻烦,Python 还有一个比greenlet更强大的并且能够自动切换任务的模块gevent。
- **原理:**当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换来继续执行。
- 由于IO操作非常耗时,经常使程序处等待状态,有了gevent为我们自动切换协程,就保证有greenlet在运行,而不是等待。
gevent安装
- pip install gevent
gevent使用
import gevent
import time
def f1(n):
for i in range(n):
print(gevent.getcurrent(),i)
# time.sleep(0.5)
gevent.sleep(0.5)
def f2(n):
for i in range(n):
print(gevent.getcurrent(),i)
# time.sleep(0.5)
gevent.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(),i)
# time.sleep(0.5)
gevent.sleep(0.5)
g1=gevent.spawn(f1,5)
g2=gevent.spawn(f2,5)
g3=gevent.spawn(f3,5)
g1.join()
g2.join()
g3.join()
'''
gevent实现协程
注意:
—— 使用gevent.sleep() 而不是普通的time.sleep()
—— gevent 中 monkey.patch_all() 可以将所有的延时 都会换成 gevent.sleep()
'''
import gevent
import time
from gevent import monkey
monkey.patch_all()
def f1(n):
for i in range(n):
print(gevent.getcurrent(),i)
time.sleep(0.5)
def f2(n):
for i in range(n):
print(gevent.getcurrent(),i)
time.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(),i)
time.sleep(0.5)
g1=gevent.spawn(f1,5)
g2=gevent.spawn(f2,5)
g3=gevent.spawn(f3,5)
g1.join()
g2.join()
g3.join()
'''
协程:利用 阻塞 来进行多任务 是使用单线程完成多任务
'''
10.6 异步编程
同步与异步
- 同步:是指代码调用IO操作时,必须等待IO操作完成猜返回的调用方式多个任务之间执行的时候要求有先后顺序,必须一个先执行完成之后,另一个才能继续执行,只有一个主线。
- 异步:是指代码调用IO操作时不必等IO操作完成就返回的调用方式。多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在的多条运行主线。
async/await实现协程
Python中使用协程最常用的库就是asyncio
- async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
- coroutine协程:协程对象,只一个使用async关键字定义的函数,他的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环中,由事件循环调用。
- event_locp 事件循环:相当于一个无限循环,我们可以把一些函数注册到这个事件循环上。当满足条件时,就会调用对应的处理方法。
- task任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程的进一步封装,其中包含任务的各种状态。
- future:代表将来执行或没有执行的人去结果,它与task没有本质区别。
快速上手
- 协程函数,定义函数的时候 async def 函数名。
- 协程对象,执行协程函数() 得到的协程对象。
import asyncio
# 1.定义协程函数
async def func():
print('我是协程')
result= func()
print(result) # 2.<coroutine object func at 0x000001F9CDFE5048> 协程对象,并没有执行
# 3.启动协程
loop=asyncio.get_event_loop() # 创建一个循环
loop.run_until_complete(result) # 将协程对象假如到事件循环中,并执行
- 如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理
Tasks
- Tasks用于并发调度协程,通过asynicio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。处了使用 asyncio.create_task() 函数以外,还可以用底层级的loop.create_task() 或 ensure_future()函数。不建议手动·实例化Task对象。
注意:asyncio.create_task() 函数在Python 3.7 中被加入。在Python 3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。
import asyncio
async def func():
print(1)
await asyncio.sleep(2)
print(2)
return '返回值'
async def mian():
print('main函数开始')
# 创建task对象
task1=asyncio.ensure_future(func())
task2=asyncio.ensure_future(func())
print('main结束')
# 当执行某协程遇到IO操作时,会自动化切换执行其它任务
ret1=await task1
ret2=await task2
print(ret1,ret2)
loop=asyncio.get_event_loop() # 创建一个事件循环
loop.run_until_complete(mian()) # 将协程对象加入到事件循环中,并执行