进程池和线程池:
正常来讲开进程和开线程都需要消耗资源,只不过两者比较的情况请下线程消耗的资源比较少,但是线程它还是需要消耗资源的,虽然线程消耗的资源少,但是如果开得线程超级多呢?几亿几十亿那种来访问,计算机超过了你所开线程的极限,那你开线程还有意义么?而且计算机肯定是扛不住的。我们设计程序应该在计算机最大的承受范围之内最大限度的利用计算机,我们应该能做到限制它能开多少进程或者开多少线程,这个时候就用到了进程池和线程池的概念:什么是池呢?在计算机行业是在保证计算机硬件安全的情况下最大限度的利用计算机,池呢它其实是降低了程序的运行效率,但是保证了计算机硬件的安全,这样子就能做到你的这个程序在这台计算机上无限的跑,无限的运行。之所以会有池这个概念,是因为硬件的发展速度跟不上软件的发展速度。
# 线程池: from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import time import os pool = ThreadPoolExecutor(5) # 括号内可以传参数指定线程池内的线程个数,也可以不传,不传默认是当前所在计算机的cpu个数乘5 def task(n): print(n) time.sleep(2) pool.submit(task, 1) # 朝线程池中提交任务 异步提交 print("主") # 测试是同步提交还是异步提交 for i in range(20): # 循环打印测试 res = pool.submit(task, i) print(res.result()) # 原地等待任务的返回结果, 这样调的话,将并发变成了串行 # 直接先把所有的任务都启起来 然后再等结果 pool = ThreadPoolExecutor(5) # 括号内可以传参数指定线程池内的线程个数,也可以不传,不传默认是当前所在计算机的cpu个数乘5 def task(n): print(n) time.sleep(2) return n**2 t_list = [] for i in range(20): # 循环打印测试是同步提交,还是异步提交 res = pool.submit(task, i) t_list.append(res) # 保证任务同时并发的提交出去,然后再拿结果 pool.shutdown() # 关闭池子等待池子中所有任务执行完毕之后,才往后执行,让打印结果有顺序 for p in t_list: # 循环打印列表 print("打印值的平方:", p.result()) # 进程池 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import time import os pool = ProcessPoolExecutor(5) # 括号内可以传参数指定线程池内的线程个数,也可以不传,不传默认是当前所在计算机的cpu个数乘5 def task(n): print(n, os.getpgid()) # 查看进程号,是否从始至终没有改变 time.sleep(2) return n**2 if __name__ == '__main__': t_list = [] for i in range(20): res = pool.submit(task, i) t_list.append(res) pool.shutdown() for p in t_list: print("打印值的平方:", p.result()) """ 池子中创建的进程或线程创建一次就不会再创建了 至始至终用的都是最初的那几个 这样的话节省了反复开辟进程或线程的资源 """
协程:
单线程下实现并发。
并发:是切换+保存状态;看起来像同时执行就可以称之为并发
协程:完全是程序员自己意淫出来的名词;这个名词表现出来的意思就是如果在单线程下你能实现并发这么一个效果;你就可以把它称之为协程。
而并发的条件是多道技术:
空间上的复用: 共用一套计算机上的设备
时间上的复用: 切换加保存状态
仔细想一下协程就是单线程下能够实现切片加保存状态,而什么时候需要用到切片+保存状态呢?当我们的程序遇到io的时候或者是这个程序长时间占用cpu了 。所以协程呢就是程序员自己通过代码自己检测程序中的io,一旦遇到io自己通过代码切换,给操作系统的感觉就是这个线程没有任何的io,就相当于你处理io这个操作你不想让电脑发现,一旦程序出现io了自己通过代码切换。对于操作系统来说这个程序一直在工作,就没有碰到io,这样下去就能够欺骗操作系统,把cup给我们的程序尽可能的去用,也就意味着我们能做到这一步,我们的程序就一直在运行态和就绪态一直切换,永远不会进入阻塞态,因为io已经被我们自己通过代码给规避掉了。
总结来讲协程就是欺骗操作系统,让操作系统误认为你的这个程序一直没有io,从而保证程序在运行状态来回切换,提升了代码的运行效率
#串行执行 0.8540799617767334 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并发执行 1.3952205181121826 import time def func1(): while True: 10000000+1 yield def func2(): g = func1() for i in range(10000000): time.sleep(100) # 模拟IO,yield并不会捕捉到并自动切换 i+1 next(g) start = time.time() func2() stop=time.time() print(stop-start) """ 需要找到一个能够识别IO的一个工具 gevent模块 注意gevent模块没办法自动识别time.sleep等io情况 需要你手动再配置一个参数 """ 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() heng() ha() print(time.time() - start)
协程实现并发:
思路:TCP的服务端之所以不能实现并发是因为一旦有个人来了,就立马进入了通讯循环里面,并且一直在这个通讯循环里面,所以说这是TCP服务端不能实现并发的原因
# 服务端.py代码演示 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() # 客户端.py代码演示: 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()