进程/线程池、协程、IO模型
1、进程池和线程池
multiprocessing为我们提供了一个Pool类,来为用户提供指定的进程数量 供用户调用: 1. 当有新的进程提交到Pool时,如果进程池没有满,那么就会创建一个新的进程执行请求, 2. 如果进程池已满,则会让请求先等待,直到有进程结束,才会执行该请求 注意: 池子中创建的进程/线程创建一次就不会再创建了 至始至终用的都是最初的那几个(可以通过查看进程号 来检验) 这样的话节省了反复开辟进程/线程的资源
通过concurrent.futures实现进程/线程池
# 代码演示(进程/线程池)+(异步回调) from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import time import os # 默认是当前计算机cpu的个数,也可以不传 不传默认是当前所在计算机的cpu个数乘5 pool = ThreadPoolExecutor(5) # pool = ProcessPoolExecutor() # 默认是当前计算机cpu的个数 def task(n): print(n,os.getpid()) # 查看当前进程号 time.sleep(2) return n**2 def call_back(n): print('拿到了异步提交任务的返回结果:',n.result()) """ 异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行 """ if __name__ == '__main__': for i in range(10): res = pool.submit(task,i).add_done_callback(call_back) # 提交任务的时候 绑定一个回调函数 一旦该任务有结果 立刻执行对于的回调函数 '''执行结果: 0 11600 1 11600 2 11600 3 11600 4 11600 拿到了异步提交任务的返回结果: 0 5 11600 拿到了异步提交任务的返回结果: 9 6 11600 拿到了异步提交任务的返回结果: 4 拿到了异步提交任务的返回结果: 1 7 11600 8 11600 拿到了异步提交任务的返回结果: 16 9 11600 拿到了异步提交任务的返回结果: 25 拿到了异步提交任务的返回结果: 49 拿到了异步提交任务的返回结果: 64 拿到了异步提交任务的返回结果: 36 拿到了异步提交任务的返回结果: 81 '''
2、协程
协程就是通过单线程实现并发
对比进程来理解: 进程:资源单位 线程:执行单位 协程:单线程下实现并发 并发 切换+保存状态 ps:看起来像同时执行的 就可以称之为并发 协程:完全是程序员自己意淫出来的名词 单线程下实现并发 并发的条件? 多道技术 空间上的复用(硬件资源的复用) 时间上的复用(切换+保存状态)
程序员自己通过代码自己检测程序中的IO
一旦遇到IO自己通过代码切换
给操作系统的感觉是你这个线程没有任何的IO
欺骗操作系统 让它误认为你这个程序一直没有IO
从而保证程序在运行态和就绪态来回切换
提升代码的运行效率
""" 需要找到一个能够识别IO的一个工具:gevent模块 注意gevent模块没办法自动识别time.sleep等io情况 需要你手动再配置一个参数:导入monkey """ from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用 所以建议写成一行,用分号隔开 from gevent import spawn import time 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) g2 = spawn(ha) # spawn会检测所有的任务 g3 = spawn(heiheihei) g1.join() g2.join() g3.join() print(time.time() - start) '''执行结果: '''
协程的应用
需求: 1. 客户端与服务端通信 2. 多个客户端(多线程)向服务端发送请求 3. 服务端用一个线程并发处理这多个请求
# 客户端 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()
# 服务端 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) if __name__ == '__main__': g1 = spawn(server1) g1.join()
3、IO模型
blocking IO 阻塞IO
nonblocking IO 非阻塞IO
IO multiplexing IO多路复用
signal driven IO 信号驱动IO
asynchronous IO 异步IO
阻塞IO模型
理解:
等待数据和拷贝数据两个阶段 都被阻塞
缺点:
阻塞导致效率低,
通过线程池可以缓解,但是需要根据响应的规模手动调节池的大小
可以考虑非阻塞IO模型
非阻塞IO
理解: 用户进程其实是需要 不断的主动询问kernel 数据准备好了没有,仅在拷贝数据的时候阻塞 缺点: 1. 循环调用recv()将大幅度推高CPU占用率; 这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况 2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作, 而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
异步IO模型
理解
进程发起read之后,立刻去执行下一行代码,
等内核处理完,并将数据放到内存后,才通知进程去拿
IO多路复用模型
理解
通过select来监测处理结果,
内核给select响应之后,由进程本身(recvfrom)接收返回的数据