协程介绍                                                                                                                    

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

需要强调的是:

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. (C#) 线程之 AutoResetEvent, EventHandle.

    AutoResetEvent 允许线程通过发信号互相通信.通常,此通信涉及线程需要独占访问的资源. 线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号.如果 AutoRese ...

  2. megacli使用

    查看raid类型 /opt/RAID/MegaCli64 -ShowSummary -a0 System Operating System: Linux version 2.6.32-220.el6. ...

  3. powershell解决win10开始菜单和通知中心无法打开

    然后通过 Ctrl + Shift + Esc 弹出任务管理器点击文件-->运行新任务 在打开的填写框里面输入 "powershell"同时勾选下方的"以管理员身份 ...

  4. 《反脆弱》:软件业现成的鲁棒性(Robust)换了个说法变成了作者的发明,按作者的理论推导出许多可笑愚蠢的原则来

    本书作者名气比较大,写过<黑天鹅><随机漫步的傻瓜>等书,据称专门研究不确定度性.本书是他以前的书的内容的延续. 所谓的反脆弱,其实软件业有现成的名词鲁棒性(Robust)就是 ...

  5. jmter安装配置

    一 JMeter 简介 JMeter 它是Apache组织的开放源代码项目,它是现在比较流行的功能和性能测试的工具.JMeter requires a fully compliant JVM 7 or ...

  6. 解决Wamp各版本中 Apache 文件列表图标无法显示

    Edit the following file manually and change the path to the icons folder (it appears times in the fi ...

  7. IOS 获取.plist文件的数据

      @property (nonatomic,strong) NSArray *apps; //获取.plist数据 /**获取plist文件的数组数据*/ -(NSArray *)apps{ if( ...

  8. 使用Sort方法对数组进行快速排序

    实现效果: 知识运用: Array类的Sort方法 public static void Sort(Array array)   // array:要排序的一维Array数组 实现代码: static ...

  9. jQuery实现轮播切换以及将其封装成插件(1)

    我们在网上经常会看到一些轮播切换的效果.轮播切换,就是在一个有限的空间中定时的像走马灯一样去播放一组图片,当然也可以通过鼠标悬停在小按钮上来切换显示.下面我们将一步一步的实现这一效果. 为保证效果,请 ...

  10. System.Web.UI

    类: System.Web.UI.Page      所以窗体继承的类