协程的初识
协程本质上就是一个线程 一个线程实现并发.如果协程中处理的所有任务都遇到了阻塞 协程就会停止 只有阻塞完成会切回来 进程间是由操作系统调控cpu 而协程是由我们自己书写的程序调控的
单个cpu: 10个任务,让你给我并发的执行这个10个任务:
- 方式一:开启多进程并发执行, 操作系统切换+保持状态.
- 方式二:开启多线程并发执行,操作系统切换+保持状态.
- 方式三:开启协程并发的执行, 自己的程序 把控着cpu 在3个任务之间来回切换+保持状态.
协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程(协程.)
单核心下处理多任务最好的方式
协程 开销小. 运行速度快. 协程会长期霸占cpu只执行我程序里面的所有任务.
并发的本质:就是切换+保持状态.
协程处理IO密集型, 计算密集型,还是串行好.
什么是协程? 单个线程并发的处理多个任务. 程序控制协程的切换+保持状态.
协程的特点:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈(保持状态)
- 附加:一个协程遇到IO操作自动切换到其它协程
工作中:
一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个
单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。
推导过程
切换 + 保持状态
# 切换 + 保持状态 def gen(): for i in range(10,1,-1): yield i def func(): obj = gen() for i in range(5): print(next(obj)) func()
利用greenlet 切换 +保持状态
用greenlet模块可以非常简单地实现这20个任务直接的切换
真正的协程模块就是使用greenlet完成的切换
# 切换 +保持状态(遇到IO不会主动切换) # switch()代表切换 from greenlet import greenlet import time def eat(name): print('%s eat 1' %name) # 2 g2.switch('taibai') # 3 time.sleep(3) print('%s eat 2' %name) # 6 g2.switch() # 7 def play(name): print('%s play 1' %name) # 4 g1.switch() # 5 切换 print('%s play 2' %name) # 8 g1=greenlet(eat) g2=greenlet(play)
最终版本
必须要jion 不然线程结束了 # g1.join() # g2.join() gevent.joinall([g1,g2])#与上面2个合并效果相同 # 最终版本: import gevent## 切换 +保持状态(遇到IO不会主动切换) from gevent import monkey monkey.patch_all() # 打补丁: 将下面的所有的任务的阻塞都打上标记 遇到就切换 def eat(name): print('%s eat 1' %name) time.sleep(2) print('%s eat 2' %name) def play(name): print('%s play 1' %name) time.sleep(1) print('%s play 2' %name) g1 = gevent.spawn(eat,'egon')#固定写法 g2 = gevent.spawn(play,name='egon') # g1.join() # g2.join() gevent.joinall([g1,g2])
来源:https://www.cnblogs.com/saoqiang/p/12388509.html