今日内容:
进程池和线程池
协程(gevent)
单线程实现并发的套接字
1. 进程池和线程池
多进程是实现并发的手段之一,需要注意的问题是:
很明显需要并发执行的任务通常要远大于核数
一个操作系统不可能无限开启进程,通常有几个核就开几个进程
进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)
此时我们就可以通过维护一个进程池来控制进程数目,规定进程数
当我们在通过
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
导入线程池和进程池的模块时,需要提交任务给创建的进程池调用
提交任务有两种方式:
同步调用:提交一个任务之后,就在原地等待,等待任务完完整整的运行完毕拿到结果后,再执行下一行代码,会导致任务是串行执行
异步调用:提交一个任务之后,不在原地等待,而是直接执行下一行代码,会导致任务是并发执行
在进程池中还有一个回调函数的概念
回调函数的应用场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,那么该函数即回调函数,如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数
回调函数的作业:我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了IO的过程,直接拿到的是任务的结果。
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor from threading import current_thread import time import requests def get(url): print('%s GET %s' % (current_thread().name, url)) time.sleep(3) response = requests.get(url) if response.status_code == 200: res = response.text else: res = '下载失败' return res def parse(future): time.sleep(1) res = future.result() print('%s 解析 %s' % (current_thread().name, len(res))) if __name__ == '__main__': urls = [ 'http://www.baidu.com', 'http://www.sina.com.cn', 'http://www.tmall.com', 'http://www.jd.com', 'http://www.python.org', 'http://www.baidu.com', 'http://www.baidu.com', ] p = ThreadPoolExecutor(7) for url in urls: future = p.submit(get, url) # 异步调用:结果futrue对象会在任务运行完毕后自动传给回调函数 future.add_done_callback(parse) # parse 会在任务运行完毕后自动触发,然后接收一个参数future对象 # 回调函数是由主进程运行的 p.shutdown(wait=True) print('主', current_thread().name)
2.协程(gevent模块)
协程目标:
在线程下实现并发
并发(多个任务看起来是同时执行就是并发):切换+保存状态协程概念:
协程是单线程实现并发
注意:协程是程序员意淫出来的,操作系统里只有进程和线程的概念(操作系统调度的是线程)在单线程下实现多个任务间遇到IO就切换可以降低单线程的IO时间,从而最大限度地提升单线程的效率
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点如下:
1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2. 单线程内就可以实现并发的效果,最大限度地利用cpu缺点如下:
1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程gevent模块
gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程
#用法 g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
例题:
from gevent import monkey monkey.patch_all() from gevent import spawn,joinall import time # 模拟边玩边吃的程序 def play(name): print('%s play 1' % name) time.sleep(3) print('%s play 2' % name) def eat(name): print('%s eat 1' % name) time.sleep(5) print('%s eat 2' % name) start = time.time() g1 = spawn(play, 'xxx') g2 = spawn(eat, 'xxx') # g1.join() # 不加join 主线程会直接结束,程序就会结束运行,得不到想要的效果 # g2.join() joinall([g1,g2]) print('主',time.time() - start)
3. 单线程实现并发的套接字
服务端
from gevent import monkey, spawn monkey.patch_all() from socket import * def comunicate(conn): while True: try: data = conn.recv(1024) conn.send(data.upper()) except ConnectionResetError: break conn.close() def server(ip, port, backlog=5): server = socket(AF_INET, SOCK_STREAM) server.bind((ip, port)) server.listen(backlog) while True: conn, client_addr = server.accept() print(client_addr) spawn(comunicate(conn)) if __name__ == '__main__': g1 = spawn(server, '127.0.0.1', 8080) g1.join()
客户端
from threading import Thread,current_thread from socket import * def client(): '''客户端连接发送函数''' client = socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) n = 0 while True: msg= '%s say holle %s'%(current_thread().name,n) n+=1 client.send(msg.encode('utf-8')) data = client.recv(1024) print(data) if __name__ == '__main__': for i in range(100): t = Thread(target=client) t.start()
以上为本次学习内容