python协程和IO多路复用
协程介绍
协程:是单线程下的并发,又称微线程,是一种用户态的轻量级线程。本身并不存在,是由程序员创造的。
需要强调的是:
1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非io操作的切换与效率无关)
优点:
1,协程的切换开销更小,属于程序级别的切换,操作系统感知不到,因而更加轻量级;
2,单线程内就可以实现并发效果,最大限度利用cpu
缺点:
1,协程的本质是在单线程下,无法利用多核,可以一个程序开启多个进程,一个进程开启多个线程,每个线程内开启协程。
2,协程指的是单个线程,因而一旦协程出现阻塞,就会阻塞整个线程。 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()
状态切换
单纯的切换(在没有io的情况或者没有重复开辟内存空间的操作),反而会降低程序的执行速度.
gevent
from gevent import monkey;monkey.patch_all()
import gevent
import time
import threading def eat():
print(threading.current_thread().getName())
print(11)
time.sleep(1)
print(22)
def play():
print(threading.current_thread().getName())
print(33)
time.sleep(1)
print(44)
g1=gevent.spawn(eat)
g2=gevent.spawn(play) gevent.joinall([g1,g2])
遇到IO主动切换
注意: from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前.
from gevent import spawn, joinall, monkey;
monkey.patch_all() import time
def task(pid):
time.sleep(0.5)
print('Task %s done' % pid)
def f1(): # 同步
for i in range(10):
task(i,)
def f2(): # 异步
g = [spawn(task, i) for i in range(10)]
joinall(g) if __name__ == '__main__':
print('f1')
f1()
print('f2')
f2()
print('DONE')
同步和异步
协程应用:
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多路复用
I/O多路复用是指单个进程可以同时监听多个网络的连接IO,用于提升效率.
I/O(input/output),通过一种机制,可以监视多个文件描述,一旦描述符就绪(读就绪和写就绪),能通知程序进行相应的读写操作。原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理。
非阻塞实例:
import socket client = socket.socket()
client.setblocking(False) # 将原来阻塞的位置变成非阻塞(报错)
# 百度创建连接: 阻塞
try:
client.connect(('www.baidu.com',80)) # 执行了但报错了
except BlockingIOError as e:
pass
# 检测到已经连接成功 # 问百度我要什么?
client.sendall(b'GET /s?wd=fanbingbing 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'))
setblock
但是非阻塞IO模型绝不被推荐。
我们不能否则其优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。
但是也难掩其缺点:
1. 循环调用recv()将大幅度推高CPU占用率,在低配主机下极容易出现卡机情况
2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
select
select是通过系统调用来监视一组由多个文件描述符组成的数组,通过调用select(),就绪的文件描述符会被内核标记出来,然后进程就可以获得这些文件描述符,进行相应的读写操作.
执行过程:
1,select需要提供要监控的数组,然后由用户态拷贝到内核态
2,内核态线性循环监控数组,每次都需要遍历整个数组
3,内核发现文件状态符符合操作结果将其返回
注意:对于要监控的socket都要设置为非阻塞的
python中使用select
r,w,e=select.selct(rlist,wlist,errlist,[timeout])
rlist,wlist,errlist均是waitable object;都是文件描述符,就是一个整数,或者拥有一个返回文件描述符的函数fileno的对象.
rlist:等待读就绪的文件描述符数组
wlist:等待写就绪的文件描述符数组
errlist:等待异常的数组
当rlist数组中的文件描述符发生可读时,(调用accept或者read函数),则获取文件描述符并添加到r数组中.
当wlist数组中的文件描述符发生可写时,则获取文件描述符添加到w数组中
当errlist数组中的的文件描述符发生错误时,将会添加到e队列中.
select的实例:
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 socket_list = [client1,client2]
conn_list = [client1,client2] 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')
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 google_repsonse(body):
print('谷歌下载结果:', body) t1 = Nb()
t1.add('www.baidu.com',baidu_repsonse)
t1.add('www.sogou.com',sogou_repsonse)
t1.add('www.google.com',google_repsonse)
t1.run()
高级版
select优点:可以跨平台使用。
缺点:1,每次调用select,都需要把fd集合由用户态拷贝到内核态,在fd多的时候开销会很大。
2,每次select都是线性遍历整个整个列表,在fd很大的时候遍历开销也很大。
操作系统检测socket是否发生变化,有三种模式(后两者在windows上不支持):
select:最多1024个socket;循环去检测。
poll:不限制监听socket个数;循环去检测(水平触发)。
epoll:不限制监听socket个数;回调方式(边缘触发)。
线程、进程、协程的区别:
python协程和IO多路复用的更多相关文章
- 多线程、多进程、协程、IO多路复用请求百度
最近学习了多线程.多进程.协程以及IO多路复用,那么对于爬取数据来说,这几个方式哪个最快呢,今天就来稍微测试一下 普通方式请求百度5次 import socket import time import ...
- 协程与IO多路复用
IO多路复用 I/O多路复用 : 通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. Python Python中有一个select模块, ...
- Python进程、线程、协程及IO多路复用
详情戳击下方链接 Python之进程.线程.协程 python之IO多路复用
- python第十周:进程、协程、IO多路复用
多进程(multiprocessing): 多进程的使用 multiprocessing是一个使用类似于线程模块的API支持产生进程的包. 多处理包提供本地和远程并发,通过使用子进程而不是线程有效地侧 ...
- Python之路,Day9 - 线程、进程、协程和IO多路复用
参考博客: 线程.进程.协程: http://www.cnblogs.com/wupeiqi/articles/5040827.html http://www.cnblogs.com/alex3714 ...
- Python 协程/异步IO/Select\Poll\Epoll异步IO与事件驱动
1 Gevent 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到 ...
- 进程,线程,协程,io多路复用 总结
并发:要做到同时服务多个客户端,有三种技术 1. 进程并行,只能开到当前cpu个数的进程,但能用来处理计算型任务 ,开销最大 2. 如果并行不必要,那么可以考虑用线程并发,单位开销比进程小很多 线程: ...
- Python 协程、IO模型
1.协程(单线程实现并发)2.I/0模型 2.1阻塞I/O 2.2非阻塞I/O 知识点一:协程 协程的目的:是想要在单线程下实现并发(并发看起来是同时运行的) 并发=多个任务间切换+保存状态(正常情况 ...
- python 多协程异步IO爬取网页加速3倍。
from urllib import request import gevent,time from gevent import monkey#该模块让当前程序所有io操作单独标记,进行异步操作. m ...
随机推荐
- Android打包混淆文件模板
# This is a configuration file for ProGuard. # http://proguard.sourceforge.net/index.html#manual/usa ...
- msql 综合练习
8.统计列印各科成绩,各分数段人数: 课程ID,课程名称,[100-85],[85-70],[70-60],[<60] 尽管表面看上去不那么容易,其实用 CASE 可以很容易地实现: SELE ...
- Struts2_HelloWorld1
打开 eclipse,新建 web 项目. 因为可能需要 jstl 表达式,所以添加 jstl需要的jar包. 下载链接:http://pan.baidu.com/s/1hr6mBI0 将jar拷贝至 ...
- [转]Android时间获取与使用
编写Android网络程序时难免会遇到手机时间不准确的问题,本文总结了一些常用的时间获取与校正方法: 转载请注明:http://blog.csdn.net/xzy2046 1.获取本机当前时间: Ti ...
- MYSQL导入excel
MYSQL使用navicat导入excel 第一步:首先需要准备好有数据的excel 第二步:选择"文件"->"另存为",保存为"CSV(逗号分 ...
- 幻灯片的JQuqey的制作效果,只要几行代码
使用jquery.KinSlideshow.js就可以很轻松的实现幻灯片效果 htm代码: [html] <div id="focusNews" style=&quo ...
- idea中使用maven方式使用jetty+cmd中使用Jetty运行(maven)Web项目
进度条件:必须是web项目 一.使用idea 导入项目或者打开(如果有可以忽略) 导入项目 . 全部next 导入成功,进行打开pom文件加入插件 <plugins> <!-- je ...
- P1024 一元三次方程求解
P1024 一元三次方程求解 #include<cstdio> #include<iostream> #include<algorithm> using names ...
- kernighan lin算法
这个算法主要用在网络节点的分割.他的思想是将一个网络节点图分割成两个相等的节点集合.为了连接两个社区的边权最小. step1:随机产生两个节点的集合A和B. step2:计算A和B中的每个节点的int ...
- 共变导数(Covariant Derivative)
原文链接 导数是指某一点的导数表示了某点上指定函数的变化率. 比如,要确定某物体的速度在某时刻的加速度,就取时间轴上下一时刻的一个微小增量,然后考察速度的增量和时间增量的比值.如果这个比值比较大,说明 ...