Python(进程池与协程)
1.进程池与线程池: 为什么要用“池”:池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承受的范围内去并发地执行任务 池子内什么时候装进程:并发的任务属于计算密集型 池子内什么时候装线程:并发的任务属于IO密集型 1、进程池 from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor 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() # 默认开启的进程数是cpu的核数 # alex,武佩奇,杨里,吴晨芋,张三 for i in range(20): p.submit(task,i) 2、线程池 from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time,os,random def task(x): print('%s 接客' %x) time.sleep(random.randint(2,5)) return x**2 if __name__ == '__main__': p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5 # alex,武佩奇,杨里,吴晨芋,张三 for i in range(20): p.submit(task,i) 2.同步、异步、阻塞、非阻塞 1、阻塞与非阻塞指的是程序的两种运行状态 阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源 非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种手段让程序即便是遇到IO操作也不会停在原地,执行其他操作,力求尽可能多的占有CPU 2、同步与异步指的是提交任务的两种方式: 同步调用:提交完任务后,就在原地等待,直到任务运行完毕后,拿到任务的返回值,才继续执行下一行代码 异步调用:提交完任务后,不在原地等待,直接执行下一行代码。等全部执行完毕后取出结果 from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor import time,os,random def task(x): print('%s 接客' %x) time.sleep(random.randint(1,3)) return x**2 if __name__ == '__main__': # 异步调用 p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5 # alex,武佩奇,杨里,吴晨芋,张三 obj_l=[] for i in range(10): obj=p.submit(task,i) obj_l.append(obj) # p.close() # p.join() p.shutdown(wait=True) (等同于p.close()(不允许向池中放新任务) + p.join()) print(obj_l[3].result()) print('主') # 同步调用 p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5 # alex,武佩奇,杨里,吴晨芋,张三 for i in range(10): res=p.submit(task,i).result() print('主') 2.协程 1、单线程下实现并发:协程 (为了提高效率;但不是说所有协程都会提升效率) 并发指的多个任务看起来是同时运行的;并发实现的本质:切换+保存状态 有效的协程在一定程度‘骗过’了CPU;通过自己内部协调,一遇到IO就切到自己的其他程序中,使得CPU以为这个程序一直在运行,从而使其更有可能处于就绪态或运行态,以更多的占用CPU。 2、实现并发的三种手段: a)单线程下的并发;由程序自己控制,相对速度快 b)多线程下的并发;由操作系统控制,相对速度较慢 c)多进程下的并发;由操作系统控制,相对速度慢 3、基于yield保存状态,实现两个任务直接来回切换,即并发的效果 (但yield不会遇到阻塞自动切程序) PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的. import time def consumer(): '''任务1:接收数据,处理数据''' while True: x=yield def producer(): '''任务2:生产数据''' g=consumer() next(g) for i in range(10000000): g.send(i) start=time.time() producer() #1.0202116966247559 stop=time.time() print(stop-start) # 纯计算的任务并发执行 import time def task1(): res=1 for i in range(1000000): res+=i yield time.sleep(10000) #yield不会自动跳过阻塞 print('task1') def task2(): g=task1() res=1 for i in range(1000000): res*=i next(g) print('task2') start=time.time() task2() stop=time.time() print(stop-start) 一、单线程下实现遇到IO切换 1、用greenlet(封装yield,遇到IO不自动切) from greenlet import greenlet import time def eat(name): print('%s eat 1' %name) time.sleep(30) g2.switch('alex') #只在第一次切换时传值 print('%s eat 2' %name) g2.switch() def play(name): print('%s play 1' %name) g1.switch() print('%s play 2' %name) g1=greenlet(eat) g2=greenlet(play) g1.switch('egon') 2、用gevent模块(封装greenlet,不处理的话,遇到自己的IO才主动切) import gevent def eat(name): print('%s eat 1' %name) gevent.sleep(5) #换成time.sleep(5),不会自动切 print('%s eat 2' %name) def play(name): print('%s play 1' %name) gevent.sleep(3) print('%s play 2' %name) g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,'alex') # gevent.sleep(100) # g1.join() # g2.join() gevent.joinall([g1,g2]) 3、用gevent模块(封装greenlet,处理的话,遇到其他IO也主动切) from gevent import monkey;monkey.patch_all() from threading import current_thread import gevent import time def eat(): print('%s eat 1' %current_thread().name) time.sleep(5) print('%s eat 2' %current_thread().name) def play(): print('%s play 1' %current_thread().name) time.sleep(3) print('%s play 2' %current_thread().name) g1=gevent.spawn(eat) g2=gevent.spawn(play) # gevent.sleep(100) # g1.join() # g2.join() print(current_thread().name) gevent.joinall([g1,g2])