协程介绍                                                                                                                    

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

需要强调的是:

  1. 1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
  2. 2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非io操作的切换与效率无关)
    优点:
      1,协程的切换开销更小,属于程序级别的切换,操作系统感知不到,因而更加轻量级;
      2,单线程内就可以实现并发效果,最大限度利用cpu
    缺点:
      1,协程的本质是在单线程下,无法利用多核,可以一个程序开启多个进程,一个进程开启多个线程,每个线程内开启协程。
      2,协程指的是单个线程,因而一旦协程出现阻塞,就会阻塞整个线程。
  3.  
  4. greenlet
  1. import greenlet
  2.  
  3. def f1():
  4. print(11)
  5. gr2.switch()
  6. print(22)
  7. gr2.switch()
  8.  
  9. def f2():
  10. print(33)
  11. gr1.switch()
  12. print(44)
  13.  
  14. # 协程 gr1
  15. gr1 = greenlet.greenlet(f1)
  16. # 协程 gr2
  17. gr2 = greenlet.greenlet(f2)
  18. gr1.switch()

状态切换

单纯的切换(在没有io的情况或者没有重复开辟内存空间的操作),反而会降低程序的执行速度.

  1. gevent
  1. from gevent import monkey;monkey.patch_all()
  2. import gevent
  3. import time
  4. import threading
  5.  
  6. def eat():
  7. print(threading.current_thread().getName())
  8. print(11)
  9. time.sleep(1)
  10. print(22)
  11. def play():
  12. print(threading.current_thread().getName())
  13. print(33)
  14. time.sleep(1)
  15. print(44)
  16. g1=gevent.spawn(eat)
  17. g2=gevent.spawn(play)
  18.  
  19. gevent.joinall([g1,g2])

遇到IO主动切换

  1.  

注意: from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前.

  1. from gevent import spawn, joinall, monkey;
  2. monkey.patch_all()
  3.  
  4. import time
  5. def task(pid):
  6. time.sleep(0.5)
  7. print('Task %s done' % pid)
  8. def f1(): # 同步
  9. for i in range(10):
  10. task(i,)
  11. def f2(): # 异步
  12. g = [spawn(task, i) for i in range(10)]
  13. joinall(g)
  14.  
  15. if __name__ == '__main__':
  16. print('f1')
  17. f1()
  18. print('f2')
  19. f2()
  20. print('DONE')

同步和异步

协程应用:

  1. from gevent import monkey
  2. monkey.patch_all() # 以后代码中遇到IO都会自动执行greenlet的switch进行切换
  3. import requests
  4. import gevent
  5.  
  6. def get_page1(url):
  7. ret = requests.get(url)
  8. print(url,ret.content)
  9.  
  10. def get_page2(url):
  11. ret = requests.get(url)
  12. print(url,ret.content)
  13.  
  14. def get_page3(url):
  15. ret = requests.get(url)
  16. print(url,ret.content)
  17.  
  18. gevent.joinall([
  19. gevent.spawn(get_page1, 'https://www.python.org/'), # 协程1
  20. gevent.spawn(get_page2, 'https://www.yahoo.com/'), # 协程2
  21. gevent.spawn(get_page3, 'https://github.com/'), # 协程3
  22. ])

爬虫

 IO多路复用 

I/O多路复用是指单个进程可以同时监听多个网络的连接IO,用于提升效率.

I/O(input/output),通过一种机制,可以监视多个文件描述,一旦描述符就绪(读就绪和写就绪),能通知程序进行相应的读写操作。原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理。

非阻塞实例:

  1. import socket
  2.  
  3. client = socket.socket()
  4. client.setblocking(False) # 将原来阻塞的位置变成非阻塞(报错)
  5. # 百度创建连接: 阻塞
  6. try:
  7. client.connect(('www.baidu.com',80)) # 执行了但报错了
  8. except BlockingIOError as e:
  9. pass
  10. # 检测到已经连接成功
  11.  
  12. # 问百度我要什么?
  13. client.sendall(b'GET /s?wd=fanbingbing HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
  14.  
  15. # 我等着接收百度给我的回复
  16. chunk_list = []
  17. while True:
  18. chunk = client.recv(8096) # 将原来阻塞的位置变成非阻塞(报错)
  19. if not chunk:
  20. break
  21. chunk_list.append(chunk)
  22.  
  23. body = b''.join(chunk_list)
  24. print(body.decode('utf-8'))

setblock

但是非阻塞IO模型绝不被推荐。

我们不能否则其优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。

但是也难掩其缺点:  

  1.   1. 循环调用recv()将大幅度推高CPU占用率,在低配主机下极容易出现卡机情况
  2.   2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
  1. 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队列中.

  1. select的实例:
  1. import socket
  2. import select
  3.  
  4. client1 = socket.socket()
  5. client1.setblocking(False) # 百度创建连接: 非阻塞
  6. try:
  7. client1.connect(('www.baidu.com',80))
  8. except BlockingIOError as e:
  9. pass
  10.  
  11. client2 = socket.socket()
  12. client2.setblocking(False) # 百度创建连接: 非阻塞
  13. try:
  14. client2.connect(('www.sogou.com',80))
  15. except BlockingIOError as e:
  16. pass
  17.  
  18. socket_list = [client1,client2]
  19. conn_list = [client1,client2]
  20.  
  21. while True:
  22. rlist,wlist,elist = select.select(socket_list,conn_list,[],0.005)
  23. # wlist中表示已经连接成功的socket对象
  24. for sk in wlist:
  25. if sk == client1:
  26. sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
  27. elif sk==client2:
  28. sk.sendall(b'GET /web?query=fdf HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n')
  29. for sk in rlist:
  30. chunk_list = []
  31. while True:
  32. try:
  33. chunk = sk.recv(8096)
  34. if not chunk:
  35. break
  36. chunk_list.append(chunk)
  37. except BlockingIOError as e:
  38. break
  39. body = b''.join(chunk_list)
  40. # print(body.decode('utf-8'))
  41. print(body)
  42. sk.close()
  43. socket_list.remove(sk)
  44. if not socket_list:
  45. break

单进程的并发

  1. import socket
  2. import select
  3.  
  4. class Req(object):
  5. def __init__(self,sk,func):
  6. self.sock = sk
  7. self.func = func
  8. def fileno(self):
  9. return self.sock.fileno()
  10.  
  11. class Nb(object):
  12. def __init__(self):
  13. self.conn_list = []
  14. self.socket_list = []
  15. def add(self,url,func):
  16. client = socket.socket()
  17. client.setblocking(False) # 非阻塞
  18. try:
  19. client.connect((url, 80))
  20. except BlockingIOError as e:
  21. pass
  22. obj = Req(client,func)
  23. self.conn_list.append(obj)
  24. self.socket_list.append(obj)
  25.  
  26. def run(self):
  27. while True:
  28. rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005)
  29. # wlist中表示已经连接成功的req对象
  30. for sk in wlist:
  31. # 发生变换的req对象
  32. sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
  33. self.conn_list.remove(sk)
  34. for sk in rlist:
  35. chunk_list = []
  36. while True:
  37. try:
  38. chunk = sk.sock.recv(8096)
  39. if not chunk:
  40. break
  41. chunk_list.append(chunk)
  42. except BlockingIOError as e:
  43. break
  44. body = b''.join(chunk_list)
  45. # print(body.decode('utf-8'))
  46. sk.func(body)
  47. sk.sock.close()
  48. self.socket_list.remove(sk)
  49. if not self.socket_list:
  50. break
  51. def baidu_repsonse(body):
  52. print('百度下载结果:',body)
  53. def sogou_repsonse(body):
  54. print('搜狗下载结果:', body)
  55. def google_repsonse(body):
  56. print('谷歌下载结果:', body)
  57.  
  58. t1 = Nb()
  59. t1.add('www.baidu.com',baidu_repsonse)
  60. t1.add('www.sogou.com',sogou_repsonse)
  61. t1.add('www.google.com',google_repsonse)
  62. t1.run()

高级版

select优点:可以跨平台使用。

    缺点:1,每次调用select,都需要把fd集合由用户态拷贝到内核态,在fd多的时候开销会很大。

       2,每次select都是线性遍历整个整个列表,在fd很大的时候遍历开销也很大。

操作系统检测socket是否发生变化,有三种模式(后两者在windows上不支持):
    select:最多1024个socket;循环去检测。
    poll:不限制监听socket个数;循环去检测(水平触发)。
    epoll:不限制监听socket个数;回调方式(边缘触发)。

线程、进程、协程的区别:

  1.  
  1.  

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. tfs2012安装

    今天正在配置tfs的服务器.要先安装net 3.5 ps1.要选择安装reportingservers 来启动报表功能.

  2. MYSQL导入excel

    MYSQL使用navicat导入excel 第一步:首先需要准备好有数据的excel 第二步:选择"文件"->"另存为",保存为"CSV(逗号分 ...

  3. phpStudy:使用localhost无法访问的解决方案

    今天遇到新的问题,很有意思,当使用Localhost时,发现报403错误: 百度找到问题所在:没有权限问题 所以我们打开phpStudy,找到配置文件“vhosts-conf”,看到的情况是这样的 接 ...

  4. oracle 11g expdp impdp详细使用方法

    11G中有个新特性,当表无数据时,不分配segment,以节省空间 解决方法如下图: 二.oracle10g以后提供了expdp/impdp工具,同样可以解决此问题 1.导出expdp工具使用方法: ...

  5. May 6th 2017 Week 18th Saturday

    A great ship asks deep water. 巨轮寻深水而航行. A great ship needs deep water so as to get enough buoyancy t ...

  6. Arduino-定义串口

    在一个老外写的代码中找到了一个非常好的定义串口的方法!   Arduino用下面这种方法定义串口可以方便的把协议应用的任意的端口,大大提高了代码的修改性和移植性.       以下是范例:       ...

  7. C4C销售订单中业务伙伴的自动决定功能Partner determination procedure

    例子:我新建一个Sales Order,account 字段选择ID为1001的Account:Porter LLC 创建成功后,观察这个Sales Order的Involved Party里,Bil ...

  8. cesium 加载倾斜摄影模型(这里有一坑)

    代码如下: // Construct the default list of terrain sources. var terrainModels = Cesium.createDefaultTerr ...

  9. TCP的可靠连接是如何产生的?

    http://bbs.csdn.net/topics/190011812 看过TCP/IP的源代码没?tcp中所谓的连接只是在tcp的tcb中存储了对端的地址信息,并且记录连接的状态,通过重发之类的来 ...

  10. Poj(1521),哈夫曼编码

    题目链接:http://poj.org/problem?id=1521 这里,网上有很多博客都有写,很多人没有建树,直接就是求一下这个哈夫曼编码的长度,的确很巧妙,我也用的这个方法,但是,几乎所有博客 ...