IO多路复用丶基于IO多路复用+socket实现并发请求丶协程
一丶IO多路复用
IO多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作
IO多路复用作用:
检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)
操作系统检测socket是否发生变化有三种模式:
select:最多1024个socket,循环去检测
poll:不限制监听socket个数,循环去检测(水平触发)
epoll:不限制监听socket个数:回调方式(边缘触发).
Python模块:
select.select
select.epoll
Python中有一个select模块,其中提供了:select丶poll丶epoll三个方法,分别调用系统的select,poll,epoll从而实现IO多路复用
注意: 网络操作丶文件操作丶终端操作等均属于IO操作,对于windows只支持socket操作,其他系统支持其他IO操作,但是无法检测普通文件操作,自动上次读取是否已经变化
二丶基于IO多路复用+socket实现并发请求(一个线程100个请求)
当我们需要向百度发送请求搜索三个关键字,我们改怎么办呢?
单线程解决并发:
方式一:
key_list = ['alex','db','sb']
for item in key_list:
ret = requests.get('https://www.baidu.com/s?wd=%s' %item)
方式二:
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 True:
chunk = client.recv(8096)
if not chunk:
break
chunk_list.append(chunk) body = b''.join(chunk_list)
print(body.decode('utf-8')) key_list = ['alex','db','sb']
for item in key_list:
get_data(item)
多线程解决并发:
import threading key_list = ['alex','db','sb']
for item in key_list:
t = threading.Thread(target=get_data,args=(item,))
t.start()
前面这几个程序在给发送连接请求时,必定会阻塞住,在哪儿等待百度给它回消息.我们可以把阻塞的地方变成非阻塞,这样可以一直给百度发送请求了,不要在哪儿傻傻的等待百度给回复了.
单线程的并发:
import socket
import select 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.sogou.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]
conn_list = [client1,client2,client3] while True:
rlist,wlist,elist = select.select(socket_list,conn_list,[],0.005)
# wlist中表示已经连接成功的socket对象
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')
conn_list.remove(sk)
for sk in rlist:
chunk_list = []
while True:
try:
chunk = sk.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'))
print('------------>',body)
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()
什么是异步非阻塞?
非阻塞,不等待
比如创建socket对某个地址进行connect丶获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作,如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可
异步,通知,执行完成之后自动执行回调函数或自动执行某些操作(通知).
比如做爬虫中向某个地址baidu.com发送请求,当请求执行完成之后自执行回调函
三丶协程
协程也可以称为"微线程",就是开发者控制线程执行流程,控制先执行某段代码然后再切换到另外函数执行代码,来回切换
需要强调的是:
1.Python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到IO或执行时间过长就会被迫交出CPU权限,切换其他线程运行)
2.单线程内开启进程,一旦遇到IO,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非IO操作的切换与效率无关)
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点如下:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2.单线程内就可以实现并发的效果,最大限度地利用CPU
缺点如下:
1.协程的本质是单线程下,无法利用多核,可以使一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
总结:
1.必须在只有一个单线程里实现并发(协程本身无法实现并发)
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.附加一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield,greenlet都无法实现,就用到了gevent模块(select机制))
Greenlet模块
安装: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
gr1 = greenlet.greenlet(f1)
# 协程 gr2
gr2 = greenlet.greenlet(f2) gr1.switch()
Gevent模块:
安装:pip3 install gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
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
])
IO多路复用丶基于IO多路复用+socket实现并发请求丶协程的更多相关文章
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程
1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...
- 并发编程:协程TCP、非阻塞IO、多路复用、
一.线程池实现阻塞IO 二.非阻塞IO模型 三.多路复用,降低CPU占用 四.模拟异步IO 一.线程池实现阻塞IO 线程阻塞IO 客户端 import socket c = socket.socket ...
- 网络编程进阶:并发编程之协程、IO模型
协程: 基于单线程实现并发,即只用一个主线程(此时可利用的CPU只有一个)情况下实现并发: 并发的本质:切换+保存状态 CPU正在运行一个任务,会在两种情况下切走去执行其他任务(切换有操作系统强制控制 ...
- python全栈开发从入门到放弃之socket并发编程之协程
一.为什么会有协程 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两种情 ...
- 协程IO多路复用
协程:单线程下实现并发并发:伪并行,遇到IO就切换,单核下多个任务之间切换执行,给你的效果就是貌似你的几个程序在同时执行.提高效率任务切换 + 保存状态并行:多核cpu,真正的同时执行串行:一个任务执 ...
- Python IO 多路复用 \协程
IO 多路复用 作用: 检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据) 即(可读/可写) IO请求时 解决并发 : 单线程 def get_data(key): cl ...
- IO多路复用、协程
一.铺垫:基于socket发送http请求 1.需求一:向百度发送请求搜索关键字“alex”,有如下两种方式: import requests ret = requests.get('https:// ...
- 协程与IO多路复用
IO多路复用 I/O多路复用 : 通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. Python Python中有一个select模块, ...
- 进击的Python【第十章】:Python的高级应用(多进程,进程间通信,协程与异步,牛逼的IO多路复用)
Python的socket高级应用(多进程,协程与异步) 一.多进程multiprocessing multiprocessing is a package that supports spawnin ...
随机推荐
- 图解Laravel的生命周期
先来张图大致理解下laravel的生命周期. 下面对应相应的代码,解释上图. //文件路径:laravel/public/index.php /** * laravel的启动时间 */ define( ...
- position:fixed;如何居中
div{ position:fixed; margin:auto; left:; right:; top:; bottom:; width:100px; height:100px; } 如果只需要左右 ...
- Exceptionless - .Net Core开源日志框架
Exceptionless - .Net Core开源日志框架 作者:markjiang7m2 原文地址:https://www.cnblogs.com/markjiang7m2/p/11020140 ...
- C#多进程并行
为了并行执行多个任务,可以启动多个进程(并行数). 下面提供两种方法,总任务数10,最大并行数4. 一.方法1 using System; using System.Collections.Gener ...
- luogup3834(主席树模板)
luogup3834(主席树模板) 给定由N个正整数构成的序列,将对于指定的闭区间查询m次其区间内第k小值.1≤N,M≤2e5. 有一个做法,是对于每个序列的前缀建一颗权值线段树,然后通过权值线段树相 ...
- 洛谷P3819 松江1843路
P3819 松江1843路 题目描述 涞坊路是一条长L米的道路,道路上的坐标范围从0到L,路上有N座房子,第i座房子建在坐标为x[i]的地方,其中住了r[i]人. 松江1843路公交车要在这条路上建一 ...
- [Xcode 实际操作]四、常用控件-(14)使用UIWebView控件加载本地HTML
目录:[Swift]Xcode实际操作 本文将演示使用网页视图,加载并渲染网页代码. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UIKit im ...
- 使用命令行+代理更新Android SDK
在无桌面的Linux上面安装Jenkins,要配置成Andorid 的持续集成环境Jenkins持续集成Android项目,需要在无桌面的Linux(ubuntu,centos)上安装Android ...
- DNS A记录 CNAME NS记录等的区别
DNS域名解析 tracetrt dig A记录 将此域名绑定到固定ip C记录 将此域名绑定到另一个域名上,通常是A记录的别名 AAAA记录 用来绑定ipv6地址 https://www.ezloo ...
- (转)linux自定义开机启动服务和chkconfig使用方法
原文:https://www.cnblogs.com/jimeper/archive/2013/03/12/2955687.html linux自定义开机启动服务和chkconfig使用方法 1. 服 ...