并发的实现原理是:切换+保存, 因此我们要实现并发,就需要为每一个任务创建一个线程,必然增加了线程创建销毁带来的开销
为了解决上述问题:
即在保证并发效果的同时,避免创建线程带来的开销问题
协程就被提了出来,协程的原理:单线程来实现多任务并发
并发:指的是多个任务同时发生,看起来像是同时都在进行
所以单线程实现并发在理论上是可行的
我们说并发就是:切换+保存,因此只要保证在单线程中,两个任务之间能够切换执行并保存状态,就能实现单线程并发
python中的生成器就具备这样一个特点,每次调用next都会回到生成器函数中执行代码,这意味任务之间可以切换,并且是基于上一次运行的结果,生成器会自动保存执行状态!
因此,我们可以利用生成器来实现并发执行:
def task1():
while True:
yield
print("task1 run")
def task2():
g = task1()
while True:
next(g)
print("task2 run")
task2()
task1 run # 先调用task1生成器,用next取一个值
task2 run # 然后在打印task2
task1 run # 循环再调用task1生成器,再用next取一个值
task2 run # 再打印task2
虽然我们实现了单线程并发两个任务,但是这效率非常低,尤其碰到计算密集型时,耗时比串行执行多一倍多
实现yield来切换,使得代码结构非常混乱,如果多个任务切换,那更加混乱,因此有人专门对yield进行了封装,这就是greenlet模块
from greenlet import greenlet
def task1(name):
print(f'{name}1 is run')
g2.switch('jack')
print(f'{name}2 is run')
g2.switch()
def task2(name):
print(f'{name}1 is run')
g1.switch()
print(f'{name}2 is run')
g1 = greenlet(task1)
g2 = greenlet(task2)
g1.switch('rose') # switch第一次执行时传参,以后就不需要再传
rose1 is run
jack1 is run
rose2 is run
jack2 is run
greenlet 模块简化了yield复杂的代码,也实现了单线程多任务的并发,但是无论yield还是greenlet都不能检测IO操作,遇到IO时同样进入阻塞状态,同样对于计算密集型任务效率也低
协程:是单线程下的并发,又称为微线程,纤程。英文名:coroutine。是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的;而线程和进程是由操作系统调度的
需要注意的是:
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
协程的实现基于gevent模块,Gevent是一个第三方库,因此需要下载,可以通过gevent实现并发编程,在gevent中用到的主要模式是greenlet,它是以C扩展模块形式接入python的轻量级协程。greenlet全部运行在主程序操作系统进程的内部,但是他们被协作式的调度。
import gevent
def task1(name):
print(f'{name}1 is run')
def task2(name):
print(f'{name}2 is run')
g1 = gevent.spawn(task1,'jack') # 实例化一个协程对象,spawn括号内第一个参数是函数名,如task1,后面可以有多个参数,可以是位置实参或者关键字实参,用逗号隔开
g2 = gevent.spawn(task2,'rose')
g1.join() #等待g1结束,之所以加join是因为,主线程结束,协程任务会立即结束
g2.join()
jack1 is run # 本质上还是串行,遇到IO操作才会切换,但是需要导入monkey补丁,如果不导入补丁,还是会 串行
rose2 is run
monkey补丁原理是把原始的阻塞方法替换为修改后的非阻塞方法,以此来实现IO自动切换
注意:一定要导入monkey模块,并实现patch_all(),才能再导入其他模块
from gevent import monkey
monkey.patch_all()
import gevent,time
def task1(name):
print(f'{name}1 is run')
time.sleep(2)
print(f'{name}2 is over')
def task2(name):
print(f'{name}1 is run')
time.sleep(1)
print(f'{name}2 is over')
g1 = gevent.spawn(task1,'jack')
g2 = gevent.spawn(task2,'rose')
g1.join()
#g2.join() # 可以只写一个执行时间最长的join
jack1 is run
rose1 is run # 遇到IO操作会自动切换
rose2 is over
jack2 is over
注意
原文:https://www.cnblogs.com/raynduan/p/11286490.html