Day 35 协程
day 35 协程
线程队列
就是一个普通的容器
Queue:就是类似JoinableQueue
LifoQueue:先进后出,类似堆栈,与Queue的区别仅在于顺序不同
PriorityQueue:具备优先级的队列,在取出数据时会比较大小,越小优先级越高,如果存入的数据是多值数据类型时,会比较第一个元素,再比较第二个元素
运算符重载
自定义对象无法使用比较运算符
这是因为自定义兑现更没有实现用于比较的方法,即_lt__和_gt
lt方法用于比较当前对象是否小于另一个对象
gt方法用于比较当前对象现是否大于另一个对象
当使用比较运算符时,会自动触发执行,并传入当前对象和比较对象,返回值是布尔值
单线程实现并发处理
GIL导致cpython中多线程无法并行执行,只能并发执行,效率低
并发是我们要是现在的最终目的,(最好是并行)
由于GIL锁多个线程只能切换执行,创建销毁线程需要消耗资源,切换线程也需要消耗资源并且最主要的问题是多线程容易出现假死问题,例如tcp服务器限制了最大线程数量为1000 如果这1000个客户端有一部分没有进行任何操作,而新的任务将无法被处理,即使cpu是空闲的,这样就造成了资源的浪费
办法:让单个线程负责处理所有任务,从而避免重复的创建销毁,已经最大数量限制的问题
如何实现
并发的本质:切换+保存状态,只要找到一种方案,能够在练歌任务之间切换执行并且保存状态,就可以实现单线程并发
python中的生成器就具备这样的特点,每次调用next都会回到生成器函数中执行代码,这意味着任务之间可以切换,并且是基于上一次运行的结果,这意味着生成器会自动保存执行状态
import time
#task1采用生成器切换并发执行,task2串行调用
def task1():
a=0
for i in range(10000000):
a+=i
yield
def task2():
g=task1()
b=0
for i in range (10000000):
b+=1
next(g)
s=time.time()
task2()
print(time.time()-s)
# task1和task2串行执行
def task1():
a = 0
for i in range(10000000):
a += i
def task2():
b = 0
for i in range(10000000):
b += 1
s=time.time()
task1()
task2()
print(time.time()-s)
单线程并发可以用生成器来实现,但是对于计算密集型任务,效率反而降低
当任务为IO密集型时,如果可以在多个任务之间切换并且可以自动监控IO操作,在这个遇到IO时,切换到另一个任务
暂且先不考虑效率问题没如果直接使用生成器来处理多任务的话,代码结构太混乱,有人专门封装了生成器,叫做Greenlet
Greenlet模块实现并发
第三方的模块,主要就是封装生成器
该模块简化了yield复杂的代码结果,实现了单线程下多任务并发,但是无论直接使用yield还是greenlet都不能检测IO操作,遇到IO时同样进入阻塞状态,同样的对于纯计算任务而言效率也是没有任何提升的
from greenlet import greenlet
import time
def f1():
res=1
for i in range(1000000):
res+=1
g2.switch()
def f2():
res = 1
for i in range(1000000):
res += 1
g1.switch()
s=time.time()
g1=greenlet(f1)
g2=greenlet(f2)
g1.switch()
print(time.time()-s)
该模块存在的问题是仅仅个封装了生成器,是语法更简单了,但是没有实现检测IO自动切换,为了实现自动切换,有人在greenlet的基础上又封装了一层Gevent
协程
协程的本质就是在但是线程下实现并发效果,即在多个任务之间做切换
协程又称之为微线程,是由应用程序自己来控制切换的任务处理方式
python的线程属于内核级别的,是有操作系统控制调度(如单线程遇到IO或执行时间过程就会被迫交出CPU执行权限,切换其他线程运行)
单线程内开启协程,一旦遇到IO,就会从应用程序级别控制切换,以此来提升效率
协程的优势:
- 协程对比线程更加轻量化,开销更小,如果我们能在当前任务遇到IO操作时切换其他任务,就可以尽可能多力用CPU,如果你的任务足够多的,就可以将CPU利用直到超时
- 避免了线程数量上限的问题
协程的缺点:
- 无法利用多核的优势,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程来尽可能提高效率
- 协程本质的单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
何时考虑用协程:IO密集型任务,且任务数量非常大
Gevent模块
第三方模块,需要自己安装
用户可以轻松通过gevent实现并发编程,在gevent中用到主要模式是greenlet,他是以C扩展模块形式接入python的轻量级协程,greenlet全部运行在主程序操作系统进程的内部,但他们被协作式的调用度
使用步骤:
- 打补丁,将原本阻塞的换成非阻塞
- 使用spawn来提交任务
- 必须使主线程不会结束,使用join或者joinall
from gevent import monkey
monkey.patch_all()
import gevent,time
def task1():
print('task1 run')
time.sleep(3)
print('task1 over')
def task2():
print('task2 run')
time.sleep(1)
print('task2 over')
g1=gevent.spawn(task1)
g2=gevent.spawn(task2)
gevent.joinall((g1,g2))
# g1.join()
# g2.join()
#遇到task1 阻塞时(sleep)会自动切换到task2
需要注意:
- 如果主进城结束了,协程任务也会立刻结束
- monkey不定的原理失败原始的阻塞方法替换为修改后的非阻塞方法,来实现IO自动切换,必须在打补丁后使用响应的功能,应该最先写补丁
总结:gevent可以是单线程的效率最大化,如果cpu有多核,可以多进程+协程