浏览器/爬虫都是socket 客户端
如何提高并发? 多线程 :IO 多进程:计算
一.IO多路复用
作用:检测多个socket 是否发生变化(是否连接成功/是否获取数据) (可读/可写)
regests 模块
ret=requests.get('https://www.baidu.com/s?wd=alex') #DNS解析,根据域名解析出IP 后面的为搜索关键字
socket
1 #socket 发送请求 2 import socket 3 cilent=socket.socket() 4 cilent.connect(("www.baidui.com",80)) 5 cilent.sendall(b'GET /s?wd=alex HTTP/1.0 host:www.baidu.com ') 6 data=cilent.recv(8096) 7 lst=[] 8 while 1: 9 if not data: 10 break 11 lst.append(data) 12 list=b"".join(lst) 13 list.decode("utf8")
b"".join(lst) 使列表结成字符串
2.多线程解决并发
1 import socket 2 import requests 3 def func(key): 4 cilent=socket.socket() 5 cilent.connect(("www.baidui.com",80)) 6 cilent.sendall(b'GET /s?wd=%s HTTP/1.0 host:www.baidu.com ' % key) 7 data=cilent.recv(8096) 8 lst=[] 9 while 1: 10 if not data: 11 break 12 lst.append(data) 13 list=b"".join(lst) 14 list.decode("utf8") 15 import threading 16 l=["zq","wa","wd"] 17 18 for item in l: 19 t1=threading.Thread(target=func,args=(item)) 20 t1.start()
缺点: 这个需要等待,因为有阻塞,会在客户端connect,recv 时刻需要等待客户端 ,怎样才能不等待呢???
IO多路复用+socket 实现并发请求 (一个线程100个请求)
这里用到 client.setblocking(False)
1 client = socket.socket() 2 client.setblocking(False) # 将原来阻塞的位置变成非阻塞(报错) 3 # 百度创建连接: 阻塞 4 5 try:#需要 避免 6 client.connect(('www.baidu.com',80)) # 执行了但报错了 7 except BlockingIOError as e: 8 pass
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IO多路复用+socket 实现并发请求
1 import socket 2 import select 3 4 client1 = socket.socket() 5 client1.setblocking(False) # 百度创建连接: 非阻塞 6 7 try: 8 client1.connect(('www.baidu.com',80)) 9 except BlockingIOError as e: 10 pass 11 12 client2 = socket.socket() 13 client2.setblocking(False) # 百度创建连接: 非阻塞 14 try: 15 client2.connect(('www.sogou.com',80)) 16 except BlockingIOError as e: 17 pass 18 19 client3 = socket.socket() 20 client3.setblocking(False) # 百度创建连接: 非阻塞 21 try: 22 client3.connect(('www.oldboyedu.com',80)) 23 except BlockingIOError as e: 24 pass 25 26 socket_list = [client1,client2,client3]#创建连接列表,有三个 27 conn_list = [client1,client2,client3]#再创建 28 29 while True: 30 rlist,wlist,elist = select.select(socket_list,conn_list,[],0.005)#rlist 31 # wlist中表示是否已经和socket建立连接对象,有返回值,可写 返回cilent 32 #rlist中表示是否已经和socket有返回数据,有返回值,可读 返回cilent 33 #[] 中 将错误信息返回空列表 34 #0.005 最大0.005秒检测错误 35 for sk in wlist: 36 if sk == client1: 37 sk.sendall(b'GET /s?wd=alex HTTP/1.0 host:www.baidu.com ') 38 elif sk==client2: 39 sk.sendall(b'GET /web?query=fdf HTTP/1.0 host:www.sogou.com ') 40 else: 41 sk.sendall(b'GET /s?wd=alex HTTP/1.0 host:www.oldboyedu.com ') 42 conn_list.remove(sk) 43 for sk in rlist: 44 chunk_list = [] 45 while True: 46 try: 47 chunk = sk.recv(8096) 48 if not chunk:# 49 break 50 chunk_list.append(chunk) 51 except BlockingIOError as e: 52 break 53 body = b''.join(chunk_list) 54 # print(body.decode('utf-8')) 55 print('------------>',body) 56 sk.close() 57 socket_list.remove(sk) 58 if not socket_list:#这里指当列表中的cilent被取到时,把他们移出作为判断 59 #直到把最后都取出 60 break
用面向对象做IO多路复用
import socket import select class Req(object): def __init__(self,sk,func): self.sock = sk self.func = func def fileno(self): return self.sock.fileno() class Nb(object): def __init__(self): self.conn_list = [] self.socket_list = [] def add(self,url,func): client = socket.socket() client.setblocking(False) # 非阻塞 try: client.connect((url, 80)) except BlockingIOError as e: pass obj = Req(client,func) self.conn_list.append(obj) self.socket_list.append(obj) def run(self): while True: rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005) # wlist中表示已经连接成功的req对象 for sk in wlist: # 发生变换的req对象 sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0 host:www.baidu.com ') self.conn_list.remove(sk) for sk in rlist: chunk_list = [] while True: try: chunk = sk.sock.recv(8096) if not chunk: break chunk_list.append(chunk) except BlockingIOError as e: break body = b''.join(chunk_list) # print(body.decode('utf-8')) sk.func(body) sk.sock.close() self.socket_list.remove(sk) if not self.socket_list: break def baidu_repsonse(body): print('百度下载结果:',body) def sogou_repsonse(body): print('搜狗下载结果:', body) def oldboyedu_repsonse(body): print('老男孩下载结果:', body) t1 = Nb() t1.add('www.baidu.com',baidu_repsonse) t1.add('www.sogou.com',sogou_repsonse) t1.add('www.oldboyedu.com',oldboyedu_repsonse) t1.run()
3.协程
概念:
进程,线程 操作系统中存在
协程,是由程序员创造出来的,不是一个单独存在的东西
协程:是为线程,对一个线程进行分片,使得线程在代码块之间来回切换执行,而不是在原来逐行执行
1 import greenlet 2 def func1(): 3 print(11) 4 f2.switch() 5 print(22) 6 f2.switch() 7 def func2(): 8 print(33) 9 f1.switch() 10 print(44) 11 f1=greenlet.greenlet(func1) 12 f2=greenlet.greenlet(func2) 13 f1.switch()
但是单独的协程没有什么作用
协程只是单纯的分片,但是用在线程上就可以节省宝贵的时间,
但是无法判断是否是IO进行分片,这时候就需要 pip3 install gevent
1 from gevent import monkey 2 monkey.patch_all()#以后代码中遇到IO都会自动执行greenlet的switch进行切换 3 import requests 4 import gevent 5 def f1(url): 6 ret=requests.get(url) 7 print(url,ret.content) 8 def f2(url): 9 ret=requests.get(url) 10 print(url,ret.content) 11 def f3(url): 12 ret=requests.get(url) 13 print(url,ret.content) 14 gevent.joinall([ 15 gevent.spawn(f1,'https://www.python.org/'),#协程1 16 gevent.spawn(f2, 'https://www.yahoo.com/'), 17 gevent.spawn(f3, 'https://www.github.com/'), 18 ])
总结:
1. 什么是协程?
协程也可以称为“微线程”,就是开发者控制线程执行流程,控制先执行某段代码然后再切换到另外函执行代码...来回切换。
2. 协程可以提高并发吗?
协程自己本身无法实现并发(甚至性能会降低)。
协程+IO切换性能提高。
3. 进程、线程、协程的区别?
4. 单线程提供并发:
- 协程+IO切换:gevent
- 基于事件循环的异步非阻塞框架:Twisted
手动实现协程:yield关键字生成器
1 def f1(): 2 print(11) 3 yield 4 print(22) 5 yield 6 print(33) 7 8 def f2(): 9 print(55) 10 yield 11 print(66) 12 yield 13 print(77) 14 15 v1 = f1() 16 v2 = f2() 17 18 next(v1) # v1.send(None) 19 next(v2) # v1.send(None) 20 next(v1) # v1.send(None) 21 next(v2) # v1.send(None) 22 next(v1) # v1.send(None) 23 next(v2) # v1.send(None)
总体总结
1 ''' 2 1.本质上socket 3 2.如何提高并发 ? 4 多进程:计算 多线程:io 5 一. 6 1.通过客户端协议也可以浏览网页 7 2.协议 遵循 8 3.间接可以用requests 模块 或者用 socket 模块来引用 9 4.cilent.setblocking(False)将原来阻塞的位置变成非阻塞(报错) 10 报错捕获一下 try except BlockingIOError as e: 11 作用:单线程遇到io 不等待 12 需要检测是否连接 13 *** io多路复用作用: 检测socket是否已将发生变化 14 (是否已经连接成功/是否已经获取数据) (可度/可写) 15 *** 16 5. select.select()[] >>报错写入 0.005 >内部最多0.005检测错误 17 6.已经实现的模块 Twisted 基于事件循环实现的异步非阻塞框架 18 还用封装实现 (没听好) 19 7.总结; 20 1.socket默认阻塞,阻塞体现在 21 connect recv 22 2. 如何让socket变成非阻塞 23 setblocking(False) 24 3. io多路复用 25 检测多个socket是否发生变化 26 4. 系统检测socket是否发生变化,三种模式 27 select "最多1024个socket;循环检测 28 poil: (主动查询)不限制监听sockrt个数;循环检测(慢)水平触发 29 epoil : (举手,自己完成主动通知) 回调方式 边缘触发 30 Python模块: 31 select.select 32 select.epoll 33 34 5.提高并发方案: 35 多进程 36 多线程 37 异步非阻塞模块(Twisted) scrapy框架(单线程完成并发) 38 5.提高并发方案: 39 控制好线程个数 40 生产者消费者模型 41 6.什么是 异步非阻塞? 42 -非阻塞 ,不等待 43 比如创建socket对某个地址进行connect、获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作。 44 如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可。 45 异步: 46 返回一个函数继续调用, 47 通知,执行完成后自动执行回调函数局或自动执行某种操作 48 7.什么是同步阻塞? 49 阻塞 ,等 50 同步,按照顺序逐步执行 51 52 不如创建socket对某个地址 53 54 55 三. 56 协程,是由程序员创造出来的不是真实存在的东西 57 由用户控制 58 协程,就是微线程,对一个线程进程分片,使得线程在代码块之间来回切换执行, 59 而不是在原来逐行执行 60 单纯的协程无用 61 协程的存在意义:类似于单线程实现并发:遇到IO + 协程的切换可以提高性能 62 3.1 63 协程+IO切换>>站起来了 pip3 install gevent 64 from gevent import monkey 65 monkey.path_all() #默认将代码遇到的IO 66 总结: 67 1.什么是协程? 68 2.协程可以提高并发么? 69 3.进程,线程,协程的区别面试题? 70 4.单线程提供并发 71 -协程+IO切换 :gevent 72 -基于事件循环的异步非阻塞框架: Twisted 73 74 也可以通过yield 75 76 以后gevent Twisted 写代码 77 三种模式: 78 select 79 poil 80 epoil