IO多路复用
1 IO多路复用:
检测多个socket是否发生变化(是否链接成功(可读),或者是否获取到信息(可写))
2.socket服务端默认是在connect,recv,阻塞
3.如何让socket编程不阻塞?
使用socket模块的setbloking方法,将阻塞的地方全部变成非阻塞,有助于提高单线程并发效率。但是会抛BlockingIOError异常,捕获即可。
4.系统检测socket是否发生了变化有三种方法
1.select:最多监听1024个socket,循环去检测
2.poll:不限制监听的次数,循环去检测(水平触发)
3.epoll:不限制监听的系数,回调方法(边缘触发)
5.提高并发的方案:
1.多进程:密集型计算
2.多线程:IO操作
3.异步非阻塞模块(twisted)基于时间循环的异步非阻塞模块
,scrapy框架(单线程完成并发)
6.什么是异步非阻塞?
非阻塞:就是不等待socket的setblocking方法
比如创建socket对某个地址进行connect,获取数据recv时会默认阻塞(链接成功后才会接受到数据),才会执行后边的操作,效率不高,会等待前一个socket执行完了之后才执行后边的socket。
但是设置setblocking(False),connect,和recv就不会等待,但是会报BlockingIOError错,捕获异常即可!
异步:起通知的作用!(烧水)
执行完之后会自动执行回调函数,或者自动执行某些功能。比如做爬虫的时候,执行完之后会自动执行回调函数!
7.什么是同步阻塞?
就是等一个socket执行完之后才会执行下一个socket,按照顺序执行。
单线程解决并发 def get_data(key): client=socket.socket() client.connect(('www.baidu.com',80)) client.sendall(b'GET /s?wd=%alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n' ) chunk_list=[] while 1: chunk=client.recv(8096) if not chunk: break chunk_list.append(chunk) boby=b''.join(chunk_list) print(boby.decode('utf8')) key_list=['alex','sb','db'] for item in key_list: get_data(item)
4.单线程的并发:select setbloking
import socket import select#检测socket是否发生了变化
client1=socket.socket() client1.setblocking(False)#将本该阻塞的变成非阻塞的 try: client1.connect(('www.baidu.com',80)) except BlockingIOError as e : pass
client2=socket.socket() client2.setblocking(False) try: client2.connect(('www.sougou.com',80)) except BlockingIOError as e : pass
client3=socket.socket() client3.setblocking(False) try: client3.connect(('www.oldboyedu.com',80)) except BlockingIOError as e : pass
socket_list=[client1,client2,client3]#检测是否服务端给我返回数据了 可读 coon_list=[client1,client2,client3]#检测是否所有的socket和服务器链接成功了 可写 while 1: rlist,wlist,elist=select.select(socket_list,coon_list,[],0.005) # wlist表示已经和服务端连接成功 for sk in wlist:#检查是否已经和服务端连接完成 if sk ==client1: sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n') elif sk ==client2: sk.sendall(b'GET /web?query=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n') else: sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n') coon_list.remove(sk)
for sk in rlist: chunk_list=[] while 1: try: chunk=sk.recv(8096) if not chunk:#chunk接收到的数据为空的话,就表示接收完毕 break chunk_list.append(chunk) except BlockingIOError as e: break boby=b''.join(chunk_list) print('--------->',boby.decode('utf8')) # print('--------->',boby) sk.close() socket_list.remove(sk) if not socket_list: break
高级版单线程并发
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\r\nhost:www.baidu.com\r\n\r\n')
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()
协程:
协程是由开发人员创造出来的一个不真实存在的东西。
协程也叫微线程,是对一个线程进行分片,让线程在代码之间相互切换执行,不再是像原来一样按照顺序执行!
单个的协程没有什么意义,无法实现并发,甚至性能会降低,它和IO 操作相结合,能大大的提高效率,会执行greenlet的switch方法!
初识协程;
pip3 install greenlet#安装greenlet模块
import greenlet def f1(): print(11) gr2.switch() print(22) gr2.switch() def f2(): print(33) gr1.switch() print(44) gr1=greenlet.greenlet(f1) gr2=greenlet.greenlet(f2) gr1.switch()
协程+IO操作:
from gevent import monkey monkey.patch_all() # 以后代码中遇到IO都会自动执行greenlet的switch进行切换 import requests import gevent def get_page1(url): ret = requests.get(url) print(url,ret.content) def get_page2(url): ret = requests.get(url) print(url,ret.content) def get_page3(url): ret = requests.get(url) print(url,ret.content) gevent.joinall([ gevent.spawn(get_page1, 'https://www.python.org/'), # 协程1 gevent.spawn(get_page2, 'https://www.yahoo.com/'), # 协程2 gevent.spawn(get_page3, 'https://github.com/'), # 协程3 ])
基于yield实现协程
def f1(): print(11) yield print(22) yield print(33) def f2(): print(55) yield print(66) yield print(77) v1 = f1() v2 = f2() next(v1) # v1.send(None) next(v2) # v1.send(None) next(v1) # v1.send(None) next(v2) # v1.send(None) next(v1) # v1.send(None) next(v2) # v1.send(None)
重点来啦!
进程,线程,协程之间的区别?
进程是计算机资源分配的最小单元,主要用来数据的隔离。
线程是计算机工作的最小单元,一个进程可以有多个线程(默认只有一个),一个应用程序有对个进程。
他们在其他语言中并没有这个概念,在python中,一般IO操作使用多线程,计算密集型计算使用多进程,这主要是由python的GIL锁造成的。
GIL锁就是在同一时刻同一进程只有一个线程才能被CPU调度,如果想使用计算机多核的优势,那就使用多进程,这就是为什么计算密集型操作使用多进程,而IO操作操作则不占用CPU。
开发人员让代码更加的强大,就出现了协程,协程本身是不存在的,由程序员自己创造的,协程可以在函数之间相互切换,本身没有太大的意义,但是和IO切换放在一起就特别厉害了,可以让一个线程分片,进而达到一个线程不会停止,一遇到IO就切换到别处,当IO操作完成之后还会切回来。在python中使用协程会遇到一个叫做greenlet的模块实现协程+IO自动切换的模块是gevent.