协程
协程的初识
协程本质上就是一个线程 一个线程实现并发.如果协程中处理的所有任务都遇到了阻塞 协程就会停止 只有阻塞完成会切回来 进程间是由操作系统调控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])
非学,无以致疑;非问,无以广识