进程池与线程池
池的简单介绍
1.为什么要用池
开线程和开进程都是需要消耗资源的,相对开进程而言,开线程消耗的资源比开进程消耗的资源少。在计算机能够承受范围之内最大限度的利用计算机。
2.什么是池?
(硬件的发展跟不上软件的速度)
池其实降低了程序的运行速率来保证计算机硬件的安全。并在计算机硬件安全的前提下最大限度的利用计算机。
3.提交任务的方式:
同步:提交任务之后 原地等待任务的返回结果,期间不做任何事。
异步:提交任务之后 不等待任务的返回结果(异步的结果通过异步回调拿), 直接执行下一行代码。
异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行。
4.创建池的优点
池子中创建的进程/线程创建一次就不会再创建了,至始至终用的都是最初的那几个。
这样的话节省了反复开辟进程/线程的资源。
进程池
池中默认的进程个数: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,并执行回调函数
线程池
池中默认线程数: cpu*5或CPU单个个数
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())
协程
1.协程
进程:资源单位;
线程:执行单位;
协程:单线程下实现并发。
2.并发
指切换+保存状态。看起来像同时执行的 就可以称之为并发。
并发的条件?
①多道技术;②空间上的复用;③时间上的复用;④切换+保存状态
3.协程的实现
进程三态中我们知道只要进程遇到阻塞就会被系统强制剥夺cpu权限,为了不被系统剥夺权限,程序员需要通过代码来检测程序中的IO。一旦发现了IO,便立马进行代码切换,保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态。
从而欺骗操作系统 让它误认为这个程序一直没有IO,可以保证程序在运行态和就绪态来回切换,提升代码的运行效率。
4.提高效率的终极方法:
当你的任务是iO密集型的情况下 , 提升效率
当你的任务是计算密集型的 , 降低效率
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 pi(): print('皮') time.sleep(3) 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多一点
基于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(实际中不常用)