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 ...
随机推荐
- Struts2_HelloWorld_7_2
第一个程序的流程图: Struts2.x 的作用:把请求和展现分开.
- Osclass-3.6.1 (Openlogic CentOS 7.2)
平台: CentOS 类型: 虚拟机镜像 软件包: osclass3.6.1 cms commercial content management ecommerce open-source 服务优惠价 ...
- T-SQL在线格式化工具
http://www.dpriver.com/pp/sqlformat.htm?ref=g_wangz
- sublim的正则匹配(待续)
ctrl+H 打开匹配模式 打开正则匹配模式 正则匹配的一些方法: 点代表的是任意字符.* 代表的是取 0 至 无限长度问号代表的是非贪婪模式.三个链接在一起是取尽量少的任意字符,一般不会这么单独写 ...
- eclipse的一些快捷键
ctrl + 1快速修复 ctrl + d 快速删除 ctrl + F11快速运行 ctrl + m 放大工作区 atl + /注释 ...
- 自己编写shave函数
import numpy def shave(I,border=None): I = I[border[0]:I.shape[0]-border[0],border[1]:I.shape[1]-bor ...
- Redis 命令学习
每天不学习点新的东西,感觉就有点会被社会淘汰掉了.也许现在学习的知识会很快忘记,下次学习用到这个知识点的时候,再回来翻记录的笔记,我想这样会比从头再学,效率会高点吧. 闲话不多聊,回归正题.今天学习r ...
- C#获取包括一二级汉字的拼音 首字母
C#获取包括一二级汉字的拼音 首字母 声母 汉字拼音转换 using System; using System.Collections.Generic; using System.Linq; usin ...
- 4、SpringBoot------邮件发送(2)
开发工具:STS 代码下载链接:https://github.com/theIndoorTrain/Springboot/tree/0d6194d6ea2d7f4e19791a3d3f3167f861 ...
- git(将现有项目加入osChina)
将现有项目加入osChina 在osChina中创建项目 注意不要初始化项目.(其实初始化也没有什么问题,可以直接clone到本地,再把项目添加进去就行了,后续操作一样的) 项目现在基本为空,得到项目 ...