协程介绍                                                                                                                    

协程:是单线程下的并发,又称微线程,是一种用户态的轻量级线程。本身并不存在,是由程序员创造的。

需要强调的是:

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多路复用的更多相关文章

  1. 多线程、多进程、协程、IO多路复用请求百度

    最近学习了多线程.多进程.协程以及IO多路复用,那么对于爬取数据来说,这几个方式哪个最快呢,今天就来稍微测试一下 普通方式请求百度5次 import socket import time import ...

  2. 协程与IO多路复用

    IO多路复用 I/O多路复用 : 通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. Python Python中有一个select模块, ...

  3. Python进程、线程、协程及IO多路复用

    详情戳击下方链接 Python之进程.线程.协程 python之IO多路复用

  4. python第十周:进程、协程、IO多路复用

    多进程(multiprocessing): 多进程的使用 multiprocessing是一个使用类似于线程模块的API支持产生进程的包. 多处理包提供本地和远程并发,通过使用子进程而不是线程有效地侧 ...

  5. Python之路,Day9 - 线程、进程、协程和IO多路复用

    参考博客: 线程.进程.协程: http://www.cnblogs.com/wupeiqi/articles/5040827.html http://www.cnblogs.com/alex3714 ...

  6. Python 协程/异步IO/Select\Poll\Epoll异步IO与事件驱动

    1 Gevent 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到 ...

  7. 进程,线程,协程,io多路复用 总结

    并发:要做到同时服务多个客户端,有三种技术 1. 进程并行,只能开到当前cpu个数的进程,但能用来处理计算型任务 ,开销最大 2. 如果并行不必要,那么可以考虑用线程并发,单位开销比进程小很多 线程: ...

  8. Python 协程、IO模型

    1.协程(单线程实现并发)2.I/0模型 2.1阻塞I/O 2.2非阻塞I/O 知识点一:协程 协程的目的:是想要在单线程下实现并发(并发看起来是同时运行的) 并发=多个任务间切换+保存状态(正常情况 ...

  9. python 多协程异步IO爬取网页加速3倍。

    from urllib import request import gevent,time from gevent import monkey#该模块让当前程序所有io操作单独标记,进行异步操作. m ...

随机推荐

  1. 转:ACCESS数据库转ORACLE数据库分享

    来源: 作者:zz 网上有很多文章介绍access转oracle数据库的方法,本人都尝试了,不是很成功,列举一下,后来人不必盲目试了,基本不成功: 1.Access-->excel-->P ...

  2. Java—运算符和流程控制语句

    运算符优先级: siwtch语句注意事项: switch 后面小括号中表达式的值必须是整型或字符型 case 后面的值可以是常量数值,如 1.2:也可以是一个常量表达式,如 2+2 :但不能是变量或带 ...

  3. Can't connect to any repository.Read timed out after 30,000 ms

    解决方法:

  4. Docker和宿主机操作系统文件目录互相隔离的实现原理

    我们知道在Docker容器里是无法访问到宿主操作系统的文件目录的,但这种隔离是怎么实现的呢? 其实一点也不神奇--利用了Linux系统的内部命令chroot. chroot能将进程的根目录设置成任意指 ...

  5. 22个必须知道的css技巧

    1.改变选中文字的背景和颜色 ::selection{ /* Safari and Opera */ background:#c3effd; color:#000; } ::-moz-selectio ...

  6. httpclient 中post请求重定向

    背景:使用httpclient 的post请求进行登录,需要重定向登录,请求重定向后的地址 在httpclient中post请求不像get请求自己可以重定向,实现方式是 判断post请求返回码是否是3 ...

  7. python图形库(1)

    python有很好图形库cv2(包含很多图形处理的算法),pylab(绘图工具模块) 这两个“模块”是肯定要配置的. 安装这两个模块可用了我不少时间. pylab它不是一个包,而是 numpy, sc ...

  8. Poj(1426),BFS

    题目链接:http://poj.org/problem?id=1426 可能数据比较水,没有用到大整数.刚刚开始的时候,想从后往前加0或者1,发现有点难写,后来想到先放一个1,再1*10,1*10+1 ...

  9. Spring转换编码utf-8方式

    方式一:修改Spring配置文件(建议使用) <mvc:annotation-driven> <mvc:message-converters register-defaults=&q ...

  10. cnn为什么会不存在vanishing gradient的问题

    之前神经网络火过一段时间,但是后来又淡出了,后来又火了,尤其是到2012年真的像发水一样. 之前为什么不火了呢,因为人们发现网络浅了吧,没什么优势.网络深了吧,又会出现vanishing gradie ...