目录
协程(Coroutine):单线程实现并发的概念
由于并发的概念,即在同段时间内同时执行任务,核心在于切换执行和保存执行状态。所以,只要在单线程中实现‘任务切换’+‘保存执行状态’就能实现所谓的并发。
优点:协程即在单线程下实现多个任务遇到I/O切换,降低单线程的IO时间,最大限度的提升单线程的效率。
注:协程的概念不存在于操作系统之中,只是单线程下实现并发的概念名,非正统。
协程 VS 操作系统
优点:
- 协程的切换开销更小,属于程序级别的切换,不涉及操作系统,更加轻量级
- 单线程内实现并发的效果,最大限度的利用cpu
缺点:
- 协程的本质是单线程下,无法利用多核。即,一个程序下开启多进程,单进程下开启多线程,单线程下开启协程。
- 协程一旦出现阻塞,会阻塞整个线程。
总结:
- 只能在单线程下实现
- 修改共享数据不需要加锁
- 程序内保存多个控制流的上下文栈
- 使用gevent实现 IO 切换协程
- 单线程下,未加入协程概念的任务执行(串行)
# 串行执行
import time
def func1():
for i in range(10000000):
i + 1
def func2():
for i in range(10000000):
i + 1
start = time.time()
func1()
func2()
stop = time.time()
print(stop - start)
'''
1.2944164276123047
'''
- 单线程下,基于yield并发执行
使用yield原因: yiled可以保存状态,即运行到yiled进行暂停,等待next操作。相对于系统操作的保存状态,yield作为代码级别,更加轻量
缺点:只能单一切换执行,不存在判断各类型 I/O 操作,降低程序的效率
'''基于yield并发执行:无法判断是否为IO操作,单一性执行的切换'''
import time
def func1():
while True:
print('func1')
10000000 + 1
yield
def func2():
g = func1()
for i in range(100):
print('func2')
time.sleep(5)
i + 1
next(g)
start = time.time()
func2()
stop = time.time()
print(stop - start)
'''
func2
func1
func2
func1
func2
func1
func2
……
'''
- gevent 模块:第三方库,实现同步并发或异步编程。
gevent模块是一个第三方库,可用于实现识别协程内部IO操作,进行协程间的切换操作。
注:使用gevent模块,并向对IO操作进行识别切换时候,必须添加 “前置打包操作(补丁)”
即:from gevent import monkey;monkey.patch_all() 加入‘;’可同行写入
用法:
spawn 组件: gobj = gevent.spawn( func,1,3,4,x=4,) 创建协程对象gobj,spawn(任务函数名,函数的位置参数/关键字参数)
gobj.join() :主线程等待gobj结束,因为是异步提交,如果不作此行为,提交结束后直接往下执行。
gobj.value() : 拿到任务函数的返回值
# '''gevent 实现协程概念'''
from gevent import monkey;monkey.patch_all()
from gevent import spawn, joinall # pip3 install gevent
import time
def play(name):
print('%s play 1' % name)
time.sleep(5)
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3)
print('%s eat 2' % name)
start = time.time()
# 异步提交
g1 = spawn(play, 'who')
g2 = spawn(eat, 'who')
# g1.join()
# g2.join()
# 等同于前两步
joinall([g1, g2])
print('主', time.time() - start)
'''
who play 1
who eat 1
who eat 2
who play 2
主 5.005553483963013
'''