1.进程池和线程池
2.异步回调
3.协程
4.基于TCP使用多线程实现高并发
一.进程池和线程池
什么是进程池和线程池:
''' 池 Pool 指的是一个容器 线程池就是用来存储线程对象的 容器 创建池子 可以指定池子里面有多少线程,如果不指定就默认为CUP个数*5 不会立即开启线程,会等到有任务提交后在开启线程 线程池,不仅帮我们管理了线程的开启和销毁,还帮我们管理任务的分配 特点: 线程池中的线程只要开启之后 即使任务结束也不会立即结束 因为后续可能会有新任务 避免了频繁开启和销毁线程造成的资源浪费 1.创建一个线程池 2.使用submit提交任务到池子中 ,线程池会自己为任务分配线程 进程池与线程池:用法都是一样的 那么为什么要使用池子: 因为使用池子可以限制并发的任务数目,在计算机可以承受的范围内去并发执行任务 那么既然提到了提交任务,那提交任务分为哪几种 同步:提交任务之后 原地等待任务的返回结果,期间不做任何事 异步:提交任务之后 不等待任务的返回结果(异步的结果怎么拿:通过异步回调拿) 直接执行下一行代码 注意: 池子中创建的进程和线程创建一个就不会创建了, 至始至终使用的都是最初的那几个,这样话会节省 开辟进程和线程的资源 '''
案例:使用代码实现线程池
1 # 线程池 2 from concurrent.futures import ThreadPoolExecutor 3 import time 4 import os 5 6 pool = ThreadPoolExecutor(5)# 括号内可以传参数指定线程池内的线程个数 7 # 也可以不传,不传默认是当前所在计算机的cpu个数*5 8 9 def task(n): 10 print(n,os.getpid())# 查看当前线程号 为了验证线程创建就不会变了 11 time.sleep(2) 12 13 for i in range(20): 14 pool.submit(task,i)# 朝池子中提交任务 属于异步提交 15 print('主')
案例:使用代码实现进程池
1 # 进程池 2 from concurrent.futures import ProcessPoolExecutor 3 import time 4 import os 5 6 pool = ProcessPoolExecutor()#不传默认是计算机cpu的个数 7 8 def task(n): 9 print(n,os.getpid())# 查看当前进程号 10 time.sleep(2) 11 12 if __name__ == '__main__': 13 14 for i in range(20): 15 pool.submit(task,i)# 朝池子中提交任务 属于异步提交 16 print('主')
二.异步回调
那么如何通过异步回调拿到结果:
如何通过异步回调拿到结果
异步回调
异步指的是任务的提交方式是异步的
异步任务的问题:
如果这个任务执行完成后会产生返回值,任务发起方该何时去获取结果
解决方案:
异步回调
异步回调指的就是一个函数,该函数会在任务后自动被调用,并且会传入Future对象 ,
通过Future对象的result()获取执行结果 ,
有了回调函数 就可以在任务完成时 及时处理它
第一种方式:
1 # 第一种方式 2 # 线程池和进程池 3 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor 4 import time 5 import os 6 7 # pool = ThreadPoolExecutor(5)# 括号内可以传参数指定线程池内的线程个数 8 # 也可以不传,不传默认是当前所在计算机的cpu个数*5 9 10 pool = ProcessPoolExecutor()# 不传默认就是计算机cpu个数 11 12 def task(n): 13 print(n,os.getpid())# 查看当前进程号 为了验证线程创建就不会变了 14 time.sleep(2) 15 return n **2# 返回值 16 17 if __name__ == '__main__': 18 19 t_list = []# 先一次性把所有的线程都起来放入这个列表里 20 for i in range(20): 21 22 res = pool.submit(task,i)# 朝池子中提交任务 属于异步提交 23 # print(res.result())# 原地等待任务的返回结果,这又变成了同步 24 t_list.append(res) 25 26 pool.shutdown()# 关闭池子,等待池子中所有的任务执行完毕之后,才会往下运行代码 27 # 这个可以让20 个线程先运行结束在拿结果 28 for p in t_list: 29 print('>>>',p.result())# 通过返回值点result拿结果
注意:但是第一种解决方案是有问题的
之前说过异步提交这个结果,一旦有结果就会有对应的机制取处理他,
但是上面的第一种方式并没有处理,上面那种方式只是用for 循环然后人为的认为
第一个就是他的结果第二个就是下一个的结果
最终版本异步回调:
1 # 线程池和进程池 2 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor 3 import time 4 import os 5 6 # pool = ThreadPoolExecutor(5)# 括号内可以传参数指定线程池内的线程个数 7 # 也可以不传,不传默认是当前所在计算机的cpu个数*5 8 9 pool = ProcessPoolExecutor()# 不传默认就是计算机cpu个数 10 11 def task(n): 12 print(n,os.getpid())# 查看当前进程号 为了验证线程创建就不会变了 13 time.sleep(2) 14 return n **2# 返回值 15 16 def call_back(x): 17 print('拿到了异步提交任务的返回结果:',x.result()) 18 #异步回调机制:当异步提交的任务有返回结果之后,会自动触发回调函数的执行 19 if __name__ == '__main__': 20 21 t_list = []# 先一次性把所有的线程都起来放入这个列表里 22 for i in range(20): 23 24 res = pool.submit(task,i).add_done_callback(call_back)# 提交任务的时候,给每个任务都绑定一个回调函数 25 # 一旦该任务有结果,就会立刻执行对应的回调函数 26 t_list.append(res)
三.协程
''' 协程 进程:资源单位 线程:执行单位 协程:单线程下实现并发 并发是什么: 切换+保存状态:看起来像同时执行的,就可以称之为并发 协程:完全就是程序员自己定义出来的名词,就是单线程下实现并发 那么并发需要哪些条件: 多道技术 空间上的复用 时间上的复用 切换+保存状态 程序员自己通过代码自己检测程序中的IO 一旦遇到IO自己通过代码切换 给操作系统的感觉是你这个线程没有任何的IO ps:欺骗操作系统 让它误认为你这个程序一直没有IO 从而保证程序在运行态和就绪态来回切换 提升代码的运行效率 切换+保存状态就一定能够提升效率吗??? 当你的任务是iO密集型的情况下 提升效率 如果你的任务是计算密集型的 降低效率 所以需要找到一个能够识别IO的一个工具 gevent模块 那么因为gevent模块没有办法自动识别time.sleep等io请况,需要自己配置一个参数 需要注意: 1.如果主线程结束了 协程任务也会立即结束。 2.monkey补丁的原理是把原始的阻塞方法替换为修改后的非阻塞方法,即偷梁换柱,来实现IO自动切换 必须在打补丁后再使用相应的功能,避免忘记,建议写在最上方 '''
案例:
1 from gevent import monkey;monkey.patch_all() # 由于该模块经常被使用,所以建议写在一行 2 from gevent import spawn 3 import time 4 5 def heng(): 6 print("哼") 7 time.sleep(2) 8 print('哼') 9 10 def ha(): 11 print('哈') 12 time.sleep(3) 13 print('哈') 14 15 def heiheihei(): 16 print('嘿嘿嘿') 17 time.sleep(5) 18 print('嘿嘿嘿') 19 20 21 start = time.time() 22 g1 = spawn(heng)# spawn会检测所有的任务 23 g2 = spawn(ha) 24 g3 = spawn(heiheihei) 25 26 g1.join() 27 g2.join()# 如果没有join直接就结束了,因为主线程都结束了 28 g3.join() 29 print(time.time() - start)
TCP单线程实现并发:
服务端:
1 from gevent import monkey;monkey.patch_all() 2 import socket 3 from gevent import spawn 4 5 server = socket.socket() 6 server.bind(('127.0.0.1',8080)) 7 server.listen(5) 8 9 def talk(conn): 10 while True: 11 try: 12 data = conn.recv(1024) 13 if not data:break 14 print(data.decode('utf-8')) 15 conn.send(data.upper()) 16 except ConnectionResetError as e: 17 print(e) 18 break 19 conn.close() 20 21 def server1(): 22 while True: 23 conn,addr = server.accept() 24 spawn(talk,conn)# 监测和调用这个函数 25 26 if __name__ == '__main__': 27 g1 = spawn(server1)# 也是监测和调用 28 g1.join()
客户端:
1 import socket 2 from threading import Thread,current_thread 3 4 5 def client(): 6 client = socket.socket() 7 client.connect(('127.0.0.1',8080)) 8 n = 0 9 while True: 10 11 data = '%s %s'%(current_thread().name,n) 12 client.send(data.encode('utf-8')) 13 res = client.recv(1024) 14 print(res.decode('utf-8')) 15 n += 1 16 17 18 for i in range(400): 19 t = Thread(target=client) 20 t.start()
四.基于TCP使用多线程实现高并发
'''
服务端
1 要有固定的ip和port
2 24小时不间断提供服务
3 能够支持并发
TCP服务端实现并发
1 将不同的功能尽量拆分成不同的函数,
拆分出来的功能可以被多个地方使用
1 将连接循环和通信循环拆分成不同的函数
2.将通信循环做成多线程
服务端:
1 import socket 2 from threading import Thread 3 4 server = socket.socket() 5 server.bind(('127.0.0.1',8080)) 6 server.listen(5) 7 8 def talk(conn): 9 while True: 10 try: 11 data = conn.recv(1024) 12 if not data:break 13 print(data.decode('utf-8')) 14 conn.send(data.upper()) 15 except ConnectionResetError as e: 16 print(e) 17 break 18 conn.close() 19 20 21 while True: 22 conn,addr = server.accept()# 监听,等待客户端的连接,阻塞态 23 t = Thread(target=talk,args=(conn,)) 24 t.start()
客户端:
1 import socket 2 client = socket.socket() 3 client.connect(('127.0.0.1',8080)) 4 while True: 5 msg = input('msg:') 6 if not msg:continue 7 client.send(msg.encode('utf-8')) 8 client.recv(1024) 9 client.close()
但是以上案例是有一个致命的缺陷的:
但是用这种方法有很大的缺点,就是万一有成千上万的客户端来连,
就需要开启对应的线程,就算线程开销再小,也是有消耗的,
这样会导致内存被占满,计算机崩溃,需要用线程池才可以解决问题
解决方案就是使用线程池:
服务端:
1 import socket 2 from concurrent.futures import ThreadPoolExecutor 3 tpool = ThreadPoolExecutor(3) 4 5 def communicate(conn): 6 while True: 7 try: 8 data = conn.recv(1024) 9 if not data: break 10 conn.send(data.upper()) 11 except ConnectionResetError as e: 12 print(e) 13 break 14 conn.close() 15 16 def server(): 17 server = socket.socket() 18 server.bind(('127.0.0.1',8080)) 19 server.listen(5) 20 21 while True: 22 conn,addr = server.accept() 23 print(addr) 24 tpool.submit(communicate,conn) 25 26 if __name__ == '__main__': 27 server()
客户端:
1 import socket 2 client=socket.socket() 3 client.connect(('127.0.0.1',8080)) 4 5 while True: 6 msg = input('msg>>>').strip() 7 if msg == 'q':break 8 if not msg:continue 9 client.send(msg.encode('utf-8')) 10 data = client.recv(1024) 11 print(data.decode('utf-8')) 12 13 client.close()