线程队列-线程池-协程-greenlet-geventcss
队列的各类方法:前端
put:往队列里面放数据python
get:从队列取数据web
put_nowait:面试
get_nowait,不阻塞,队列还有值的时候,直接跳过,没有值的时候报错(但要是get的话,会阻塞),正则表达式
full,检查队列是否满了,返回bool值安全
empty:检查队列是否空了,返回bool值多线程
qsize:输出队列的大小,并发
自带锁,数据安全,app
queue队列:使用import queue,用法与进程Queue同样,先进先出
queue.LifoQueue:后进先出,(栈)
queue.Priority():存储数据时可设置优先级的队列,
from queue import PriorityQueue q=PriorityQueue() #优先级队列 q.put(12,'bbb') q.put(2,'abb') q.put(20,'vbb') q.put(1,'bbb') print(q.get()) print(q.get()) print(q.get()) print(q.get()) 输出: 按数字大小输出,其实是按ascii码的前后输出的, 1 2 12 20
Threading 没有线程池,multiprocessing,有进程池,concurrent.futures:帮助开发人员管理线程池和进程池,
from threading import currentThread,get_ident
from concurrent.futures import ThreadPoolExecutor(启动线程池的类)
from concurrent.futures import ProcssPoolExecutor(启动进程池的类)
基本介绍
1 #1 介绍 2 concurrent.futures模块提供了高度封装的异步调用接口 3 ThreadPoolExecutor:线程池,提供异步调用 4 ProcessPoolExecutor: 进程池,提供异步调用 5 Both implement the same interface, which is defined by the abstract Executor class. 6 7 #2 基本方法 8 #submit(fn, *args, **kwargs) 9 异步提交任务 10 11 #map(func, *iterables, timeout=None, chunksize=1) 12 取代for循环submit的操做 13 14 #shutdown(wait=True) 15 至关于进程池的pool.close()+pool.join()操做 16 wait=True,等待池内全部任务执行完毕回收完资源后才继续 17 wait=False,当即返回,并不会等待池内的任务执行完毕 18 但无论wait参数为什么值,整个程序都会等到全部任务执行完毕 19 submit和map必须在shutdown以前 20 21 #result(timeout=None) 22 取得结果 23 24 #add_done_callback(fn) 25 回调函数
submit,map,shutdown的结合用法
1 import time 2 from threading import currentThread,get_ident #查看当前的线程(currentThread) 3 from concurrent.futures import ThreadPoolExecutor 4 5 def func(i): 6 print('in %s' %currentThread(),get_ident(),i)#显示当前的线程(包含线程ID,跟线程名) 7 #输出的线程ID有重复的,证实用到了回收的线程 8 9 t=ThreadPoolExecutor(5)#实例化一个线程,开启线程池,这里5个线程 10 11 for i in range(20): 12 t.submit(func,i)#异步提交任务,这里进源码看submit,函数传参的问题 13 t.shutdown()#至关于进程池的Pool.close()+Pool.join(),默认为True,为真时,才起做用, 14 print('main:',currentThread(),get_ident()) #主线程 15 16 17 两个实现起线程池的方法的区别,对比输出结果, 18 19 20 from threading import current_thread,get_ident 21 from concurrent.futures import ThreadPoolExecutor 22 23 def func(i): #必需要进行传参, 24 print(888,current_thread()) 25 26 t=ThreadPoolExecutor(5)#线程池,起5个线程, 27 t.map(func,range(20))#map至关于,把可迭代对象的每一个值取出来传给func函数,
result(timeout)
1 import os,time 2 from concurrent.futures import ThreadPoolExecutor 3 4 def func(i): 5 time.sleep(0.5) 6 print(i,) 7 return i+20 8 9 t=ThreadPoolExecutor(5) 10 lst=[] 11 for i in range(20): 12 ret=t.submit(func,i) 13 # print(ret.result()) #取得结果, 14 lst.append(ret) 15 t.shutdown() 16 for ret in lst: #对比上面的结果,上面的又变成同步的了,因此须要建立一个了列表 17 print(ret.result()) 18 print('主线程')
add_done_callback(fn):回调函数
线程池的回调函数执行,是在线程池里的各个线程.
from threading import get_ident from concurrent.futures import ThreadPoolExecutor def func(i): print(i,get_ident()) return get_ident() def back(fn): print('in back',fn.result())#返回的是函数的地址,取值的话,须要.result() t=ThreadPoolExecutor(5) for i in range(20): t.submit(func,i).add_done_callback(back)#回调函数,
进程版本的各个例子
import os from concurrent.futures import ProcessPoolExecutor def func(i): print(i,os.getpid()) return i*3 def back(fn): print(fn.result(),os.getpid()) if __name__ == '__main__': p=ProcessPoolExecutor(5) lst=[] for i in range(20): ret=p.submit(func,i)#得到返回值 lst.append(ret) # p.submit(func,i).add_done_callback(back)#回调函数 p.shutdown() for ret in lst: ret.result()
进程/线程池总结:
multiprocessing模块自带进程池的
threading模块是没有线程池的
concurrent.futures 进程池 和 线程池
高度封装
进程池/线程池的统一的使用方式
建立线程池/进程池 ProcessPoolExecutor ThreadPoolExecutor
ret = t.submit(func,arg1,arg2....) 异步提交任务
ret.result() 获取结果,若是要想实现异步效果,应该是使用列表
map(func,iterable)
shutdown close+join 同步控制的
add_done_callback 回调函数,在回调函数内接收的参数是一个对象,须要经过result来获取返回值
回调函数仍然在主进程中执行 进程池
回调函数是在池子里的各个线程执行 线程池
概念性的面试题
web框架
爬虫/自动化开发
爬虫 : 访问大量的网页,对网页代码进行处理
正则表达式
字符串处理
前端
并发
运维 : 一堆机器 一堆程序 你去维护
自动化开发/运维开发 : 开发一些程序 让机器的维护/程序的维护自动化起来
运维基础
python的基础开发
并发
前端
协程
以前咱们学习了线程、进程的概念,了解了在操做系统中进程是资源分配的最小单位,线程是CPU调度的最小单位。按道理来讲咱们已经算是把cpu的利用率提升不少了。可是咱们知道不管是建立多进程仍是建立多线程来解决问题,都要消耗必定的时间来建立进程、建立线程、以及管理他们之间的切换。
随着咱们对于效率的追求不断提升,基于单线程来实现并发又成为一个新的课题,即只用一个主线程(很明显可利用的cpu只有一个)状况下实现并发。这样就能够节省建立线进程所消耗的时间。
为此咱们须要先回顾下并发的本质:切换+保存状态
cpu正在运行一个任务,会在两种状况下切走去执行其余的任务(切换由操做系统强制控制),一种状况是该任务发生了阻塞,另一种状况是该任务计算的时间过长
ps:在介绍进程理论时,说起进程的三种执行状态,而线程才是执行单位,因此也能够将上图理解为线程的三种状态
对于单线程下,咱们不可避免程序中出现io操做,但若是咱们能在本身的程序中(即用户程序级别,而非操做系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另一个任务去计算,这样就保证了该线程可以最大限度地处于就绪态,即随时均可以被cpu执行的状态,至关于咱们在用户程序级别将本身的io操做最大限度地隐藏起来,从而能够迷惑操做系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给咱们的线程。
协程的本质就是在单线程下,由用户本身控制一个任务遇到io阻塞了就切换另一个任务去执行,以此来提高效率。为了实现它,咱们须要找寻一种能够同时知足如下条件的解决方案:
#1. 能够控制多个任务之间的切换,切换以前将任务的状态保存下来,以便从新运行时,能够基于暂停的位置继续执行。 #2. 做为1的补充:能够检测io操做,在遇到io操做的状况下才发生切换
协程介绍
协程:是单线程下的并发,又称微线程,纤程.英文名:Coroutine,
协程是一种用户态的轻量级线程,即协程是由用户程序本身控制调度的.
注意:
python的线程属于内核级别的,即由操做系统控制调度,(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其余线程执行)
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操做系统)控制切换,依次来提高效率(非io操做的切换去效率无关)
对比操做系统控制线程的切换,用户在多线程内控制协程的切换,
优势:
1,协程的切换开销更小,属于程序级别的切换,操做系统彻底感知不到,于是更加轻量级
2,多线程内就能够实现并发的效果,最大限度地利用cpu.
缺点:
1,协程的本质是单线程下,没法利用多核,能够是一个程序开启多个进程,每一个进程内开启多个线程,每一个线程内开启协程,
2,协程指的是单个线程,于是一旦协程出现阻塞,将会阻塞整个线程,
总结协程特色:
1,必须在只有一个单线程里实现并发
2,修改共享数据不需加锁
3,用户程序里本身保存多个控制流的上下文栈
4,附加:一个协程遇到IO操做自动切换到其它协程(如何实现检测IO,yield,greenlet都没法实现,就用到了gevent模块)
安装:在cmd里输入命令:pep3 install greenlet
yield在两个函数之间的应用.
解说yield的执行
1 def func(): 2 print(22) 3 x=yield 'aaa' 4 print(x) 5 print(555) 6 y=yield 'bbb' 7 print(y) 8 print(666) 9 10 g=func() #拿到一个生成器函数,执行到此行,没有打印任何东西 11 print(next(g)) #执行到此,打印22,拿到yield的返回值:aaa,共打印:222,aaa,而后阻塞,没有再输出 12 print(g.send(333)) #执行到此,遇到send,继续执行把333,传给第三行的yield,而后打印333,打印555,拿到yield的返回值:bbb,共输出:333,555,bbb 13 14 # 输出: 15 # 22 16 # aaa 17 # 333 18 # 555 19 # bbb
1 def func1(): 2 while 1: 3 print(222) 4 x=yield 5 print(x) 6 print(666) 7 8 def func2(): 9 g=func1() 10 next(g) 11 for i in range(3): 12 g.send(i) 13 func2() 14 15 # 输出: 16 # 222 17 # 0 18 # 666 19 # 222 20 # 1 21 # 666 22 # 222 23 # 2 24 # 666 25 # 222
用yield实现消费者和生产者模型,
1 def consumer(): 2 while True: 3 x = yield 4 print(x) 5 def producer(): 6 g = consumer() 7 next(g) # 预激 8 for i in range(10): 9 g.send(i) 10 producer()
yield只有程序之间的切换,没有重利用任何IO操做的时间,
程序执行的上下文切换的工具.
greenlet,gevent,第三方模块,安装,
1,cmd命令行:输出命令:pip3 insatll greenlet(若是是python2版本的,就是pip2)
2,在pycharm里面,文件-->设置-->项目-->Project Iterpreter-->右上角(加号)-->在输入框输入要安装的模块-->左下角-->Install Package-->回上一级菜单-->肯定-->重启pycharm(或者笔记本)
greenlet程序上下文切换
使用greenlet模块
1 import time 2 from greenlet import greenlet 3 def eat(): 4 print('吃') 5 time.sleep(1) 6 g2.switch()#执行到此,切换到play函数,且下次今后处开始接着执行 7 print('吃完了') 8 time.sleep(1) 9 g2.switch()#执行到此,切换到play函数, 10 def play(): 11 print('玩儿') 12 time.sleep(1) 13 g1.switch() #执行到此,切换到eat函数, 14 print('玩美了') 15 time.sleep(1) 16 g1=greenlet(eat)#greenlet是一个类,此行是实例化了一个对象,传一个eat函数 17 g2=greenlet(play) 18 g1.switch() #切换,开启g1,此处再也不是start(),要注意, 19 20 # 输出: 21 # 吃 22 # 玩儿 23 # 吃完了 24 # 玩美了
gevent模块
安装:pip3 install gevent(cmd命令行处输入命令安装)
greenlet是gevent的低层
gevent是基于greenlet实现的
python代码在控制程序的切换
使用gevent来写greenlet的例子
import gevent import time def eat(): print('吃') time.sleep(2) print('吃完了') def play(): print('玩儿') time.sleep(1) print('玩儿美了') g1 = gevent.spawn(eat) #此处没有详细解释spawn g2 = gevent.spawn(play) #单纯的代码写到这里,执行的话,什么都没有打印(输出), # gevent.sleep(0.5)#可使用此行代码启动,遇到IO才切换, # g1.join()#使用join是等待eat函数执行完, # g2.join() gevent.joinall([g1,g2])#同上面的g1或g2.join()是同样的, # 输出: # 吃 # 玩儿 # 玩儿美了 # 吃完了
gevent帮你作了切换,作切换是有条件的,遇到IO才切换
gevent不认识除了gevent这个模块内之外的IO操做
使用join能够一直阻塞直到协程任务完成
帮助gevent来认识其余模块中的阻塞
from gevent import monkey;monkey.patch_all()写在其余模块导入以前