进程池 线程池 协程
一、进程、线程池
#为什么用
开进程开线程都需要消耗资源消耗时间的,只不过两者比较的情况线程消耗的资源比较少
在成千上万个任务需要被执行的时候,我们可以去创建成千上万个进程么?
就算你比较二,建了无数个进程线程,系统敢让你执行么?除非它自己不要命了,辛辛苦苦创建出来还不能执行,气不气?
我们需要一个池子,根据计算机状况最大限度的限制进程线程数量,在计算机能够承受范围之内最大限度的利用计算机
#什么是池?
(硬件的发展跟不上软件的速度)
池其实是降低了程序的运行效率 但是保证了计算机硬件的安全
最大限度的限制进程线程数量,在保证计算机硬件安全的情况下最大限度的利用计算机
#提交任务的方式:
同步: 提交任务之后 原地等待任务的返回结果 期间不做任何事
异步: 提交任务之后 不等待任务的返回结果(异步的结果怎么拿???) 直接执行下一行代码
异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行
#创建的特点
池子中创建的进程/线程创建一次就不会再创建了
至始至终用的都是最初的那几个
这样的话节省了反复开辟进程/线程的资源
1、进程池
池中默认进程数: cpu个数
from concurrent.futures import ProcessPoolExecutor import time import os pool = ProcessPoolExecutor(3) # 默认是当前计算机cpu的个数 def task(n): print(n,os.getpid()) # 查看当前进程号,发现前三个和后三个进程号一样,进程池中三个进程重复利用 time.sleep(2) return n**2,100 def call_back(n): print('拿到了异步提交任务的返回结果:',n.result()) if __name__ == '__main__': for i in range(6): pool.submit(task,i).add_done_callback(call_back) # 提交任务的时候,绑定一个回调函数call_back, # 一旦该任务有结果 pool.submit(task,i)即为task函数的返回值 # add_done_callback 立刻将该结果传给回调函数call_back,并执行回调函数 ''' 0 9304 1 9060 2 12240 3 9304 拿到了异步提交任务的返回结果: (0, 100) 4 9060 拿到了异步提交任务的返回结果: (1, 100) 5 12240 拿到了异步提交任务的返回结果: (4, 100) 拿到了异步提交任务的返回结果: (9, 100) 拿到了异步提交任务的返回结果: (16, 100) 拿到了异步提交任务的返回结果: (25, 100) '''
2、线程池
池中默认线程数: cpu*5
2.1关闭池子后一起输出结果
pool.shutdown() #等待池子中所有的任务执行完毕之后 才会往下运行代码
from concurrent.futures import ThreadPoolExecutor import time pool = ThreadPoolExecutor(3) # 不传默认是当前所在计算机的cpu个数乘5 def task(n): print(n) time.sleep(2) return n**2 t_list = [] for i in range(6): res = pool.submit(task,i) # print(res.result()) # 原地等待任务的返回结果,这一句加上异步就变同步了,不能要 t_list.append(res) pool.shutdown() # 关闭池子 等待池子中所有的任务执行完毕之后 才会往下运行代码 for p in t_list: print('>>>:',p.result()) ''' 0 1 2 3 4 5 >>>: 0 >>>: 1 >>>: 4 >>>: 9 >>>: 16 >>>: 25 '''
2.2异步回调输出结果
from concurrent.futures import ThreadPoolExecutor from threading import currentThread import time pool = ThreadPoolExecutor(3) # 括号内可以传参数指定线程池内的线程个数,不传默认是当前所在计算机的cpu个数乘5 def task(n): print(n,currentThread().name) #输出线程号会发现还是那3个在循环使用 time.sleep(2) return n**2 def call_back(n): print('拿到了异步提交任务的返回结果:',n.result()) for i in range(6): pool.submit(task,i).add_done_callback(call_back) # 提交任务的时候,绑定一个回调函数call_back, # 一旦该任务有结果 pool.submit(task,i)即为task函数的返回值 # add_done_callback 立刻将该结果传给回调函数call_back,并执行回调函数 ''' 0 ThreadPoolExecutor-0_0 1 ThreadPoolExecutor-0_1 2 ThreadPoolExecutor-0_2 拿到了异步提交任务的返回结果: 0 3 ThreadPoolExecutor-0_0 拿到了异步提交任务的返回结果: 1 4 ThreadPoolExecutor-0_1 拿到了异步提交任务的返回结果: 4 5 ThreadPoolExecutor-0_2 拿到了异步提交任务的返回结果: 9 拿到了异步提交任务的返回结果: 16 拿到了异步提交任务的返回结果: 25 '''
二、协程
1.协程
进程:资源单位
线程:执行单位
协程:单线程下实现并发(完全是程序员自己意淫出来的名词)
优点:
协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级 单线程内就可以实现并发的效果,最大限度地利用cpu
2.协程的实现:
进程三态中我们知道只要进程遇到阻塞就会被系统强制剥夺cpu权限,怎样不让剥夺呢?
当然是我们自己主动去发现io并尽量改正,好好表现争取更多cpu资源
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中,即用户程序级别,主动监控IO,尽量不让操作系统操心
当单线程下的多个任务在一个任务遇到io阻塞时就立刻切换到另外一个任务去计算
这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来
从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程,大大提升效率
4.提高效率的终极方法:
多进程下开多线程
多线程下再开协程
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)
#yield可以保存状态,然后next来取结果,我们能不能通过yield来切换并保存状态呢?
运行发现不能提高效率也不能识别IO更别说自动切换了,浪费感情
# 效率还没串行高,并且还不能识别IO更别说自动切换了 import time def func1(): while True: 10000000+1 yield def func2(): g=func1() for i in range(10000000): # time.sleep(100) # 模拟IO,打开后,程序不会运行了,也就是说yield并不会捕捉IO并自动切换 i+1 next(g) start=time.time() func2() stop=time.time() print(stop-start)
2.1 gevent模块
from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用 所以建议写成一行 小猴可以监控任何IO
from gevent import spawn #这个可以识别少量IO主要用来切换加保存,监控交给小猴来做
from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用 所以建议写成一行 from gevent import spawn import time """ 注意gevent模块没办法自动识别time.sleep等io情况 需要你手动再配置一个参数,即小猴模块,小猴可以发现所有IO """ def heng(): print("哼") time.sleep(2) print('哼哼') def ha(): print('哈') time.sleep(3) print('哈哈') def heiheihei(): print('嘿') time.sleep(5) print('嘿嘿') start = time.time() g1 = spawn(heng)#spawn类似一个列表,把所有函数都放进去,程序一运行,他监测所有任务,一旦发现有IO立马换任务执行 g2 = spawn(ha) # spawn对任务的切换是在代码层面上,操作系统不会发现,只会以为该进程一直在干活,就不会剥夺CPU权限 g3 = spawn(heiheihei) #spawn只要传进函数名字会自动加括号调用 # g1.join() # g2.join() g3.join() #如果你不等,这里可就这一个主线程,直接就往下执行输出代码,代码完程序立刻停止,一个函数也不会执行 print(time.time() - start) #5s多一点
2.2 基于gevent模块的TCP服务端单线程实现并发
#服务端
from gevent import monkey;monkey.patch_all() import socket from gevent import spawn server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn): while True: try: data = conn.recv(1024) if len(data) == 0:break print(data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() def server1(): while True: conn, addr = server.accept() spawn(talk,conn) #监控talk中的IO,并传入参数conn if __name__ == '__main__': g1 = spawn(server1) #监控accept的IO g1.join()
#客户端
import socket from threading import Thread,current_thread def client(): client = socket.socket() client.connect(('127.0.0.1',8080)) n = 0 while True: data = '%s %s'%(current_thread().name,n) client.send(data.encode('utf-8')) res = client.recv(1024) print(res.decode('utf-8')) n += 1 for i in range(400): t = Thread(target=client) t.start()
三、IO模型
Stevens在文章中一共比较了五种IO Model:
* blocking IO 阻塞IO
* nonblocking IO 非阻塞IO
* IO multiplexing IO多路复用 (面试会问到看一看)
* asynchronous IO 异步IO
* signal driven IO 信号驱动IO
由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。