进程池线程池与同步异步阻塞非阻塞
无论是开进程还是开线程都消耗资源,开线程比开进程消耗的资源要小
1、池:为了减缓计算机硬件的压力,避免计算机硬件设备崩溃。虽然减轻了计算机的压力,但是一定程度上降低了持续的效率
2、为什么要用“池”: 池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承受的范围内去并发地执行任务
3、进程池线程池:为了限制开设的进程数和线程数,从而保证计算机硬件的安全
4、池子内什么时候装进程:并发的任务属于计算密集型
池子内什么时候装线程:并发的任务属于IO密集型
①进程池
from concurrent.futures import ProcessPoolExecutor import time, os, random def task(x): print('%s 接客' % os.getpid()) time.sleep(random.randint(2, 5)) return x**2 if __name__ == '__main__': p = ProcessPoolExecutor(5) # ()内不加参数默认开启的进程数是cpu的核数 for i in range(20): p.submit(task, i)
②线程池
from concurrent.futures import ThreadPoolExecutor import time,random def task(x): print('%s 接客' % x) time.sleep(random.randint(2, 5)) return x ** 2 if __name__ == '__main__': p = ThreadPoolExecutor(4) # ()内不加参数默认开启的线程数是cpu的核数*5 for i in range(20): p.submit(task, i)
二、同步异步阻塞非阻塞(*****)
1、同步与异步:提交任务的两种方式
同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才继续执行下一行代码
异步调用:提交完任务后,不在原地等待,直接执行下一行代码
from concurrent.futures import ThreadPoolExecutor import time, random def task(x): print('%s 接客' % x) time.sleep(random.randint(1, 3)) return x ** 2 # 异步调用 if __name__ == '__main__': p = ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5 obj_l = [] for i in range(10): obj = p.submit(task, i) obj_l.append(obj) p.shutdown(wait=True) # 等待所有任务运行完毕在执行结果obj调result(),这里拿结果不同等待 print(obj_l[0].result()) print('主') # 同步调用 if __name__ == '__main__': p = ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5 for i in range(10): res = p.submit(task, i).result() # 提交任务在原地等结果,与阻塞没有关系。 # 拿到结果之后再继续执行 print(res) print('主')
2、阻塞与非阻塞:程序的两种运行状态
阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源
非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种手段让程序即便是遇到IO操作也不会停在原地执行其他操作,力求尽可能多的占有CPU
三、协程(单线程下实现并发)
1、并发实现的本质:切换+保存状态
2、并发:看起来是同时运行,切换+保存状态
并行:真正意义上的同时运行,只有在多cpu的情况下才能 实现并行,4个cpu能够并行4个任务
串行:一个人完完整整地执行完毕才运行下一个任务
协程:单线程下实现并发,自己通过代码层面监测自己的io行为, 并自己实现切换+保存状态,让操作系统误认为这个线程没有io
①客户端
from threading import Thread, current_thread import socket def client(): client = socket.socket() client.connect(('127.0.0.1', 8080)) n = 1 while True: data = '%s %s' % (current_thread().name, n) n += 1 client.send(data.encode('utf-8')) info = client.recv(1024) print(info) if __name__ == '__main__': for i in range(100): t = Thread(target=client) t.start()
②服务端
from gevent import monkey; monkey.patch_all() from gevent import spawn import socket def communicate(conn): while True: try: data = conn.recv(1024) if len(data) == 0: break print(data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError: break conn.close() def server(): server = socket.socket() server.bind(('127.0.0.1', 8080)) server.listen(5) while True: conn, addr = server.accept() spawn(communicate, conn) if __name__ == '__main__': s1 = spawn(server) s1.join()
请仔细理解如下的通俗描述:
有一个老板想要开个工厂进行生产某件商品(例如剪子)
他需要花一些财力物力制作一条生产线·这个生产线上有很多的器件以及材料这些所有的为了能够生产剪子而准备的资源称之为:进程
只有生产线是不能够进行生产的所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程。这个老板为了提高生产率,想到3种办法:
1、在这条生产线上多招些工人,一起来做剪子,这样效率是成倍增长·即单进程 多线程方式
2、老板发现这条生产线上的工人不是越多越好因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了·即多进程多线程方式
3、老板发现现在已经有了很多个生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作),那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是 协程方式
简单总结:
①进程是资源分配的单位
②线程是操作系统调度的单位
③进程切换需要的资源很最大·效率很低
④线程切换需要的资源一般效率一般(当然了在不考虑GIL的情况下)
⑤协程切换任务资源很小·效率高
⑥多进程、多线程根据cpu核数不一样可能是并行的·但是协程是在一个线程中所以是并发
四、gevent模块使用
from gevent import monkey; monkey.patch_all() 监测代码中所有io行为
from gevent import monkey; monkey.patch_all() # 监测代码中所有io行为 from gevent import spawn # gevent本身识别不了time.sleep等不属于该模块内的io操作 import time def heng(name): print('%s 哼' % name) time.sleep(2) print('%s 哼' % name) def ha(name): print('%s 哈' % name) time.sleep(2) print('%s 哈' % name) start = time.time() s1 = spawn(heng, 'egon') s2 = spawn(ha, 'tank') s1.join() s2.join() print('主', time.time()-start) # 结果为 # egon 哼 # tank 哈 # egon 哼 # tank 哈 # 主 2.1111209392547607