一丶IO多路复用

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

  IO多路复用作用:

    检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)

    操作系统检测socket是否发生变化有三种模式:

      select:最多1024个socket,循环去检测

      poll:不限制监听socket个数,循环去检测(水平触发)

      epoll:不限制监听socket个数:回调方式(边缘触发).

    Python模块:

      select.select

      select.epoll

  Python中有一个select模块,其中提供了:select丶poll丶epoll三个方法,分别调用系统的select,poll,epoll从而实现IO多路复用

  注意: 网络操作丶文件操作丶终端操作等均属于IO操作,对于windows只支持socket操作,其他系统支持其他IO操作,但是无法检测普通文件操作,自动上次读取是否已经变化

二丶基于IO多路复用+socket实现并发请求(一个线程100个请求)

  当我们需要向百度发送请求搜索三个关键字,我们改怎么办呢?

  单线程解决并发:

方式一:

  1. key_list = ['alex','db','sb']
  2. for item in key_list:
  3. ret = requests.get('https://www.baidu.com/s?wd=%s' %item)

方式二:

  1. def get_data(key):
  2. # 方式二
  3. client = socket.socket()
  4.  
  5. # 百度创建连接: 阻塞
  6. client.connect(('www.baidu.com',80))
  7.  
  8. # 问百度我要什么?
  9. client.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
  10.  
  11. # 我等着接收百度给我的回复
  12. chunk_list = []
  13. while True:
  14. chunk = client.recv(8096)
  15. if not chunk:
  16. break
  17. chunk_list.append(chunk)
  18.  
  19. body = b''.join(chunk_list)
  20. print(body.decode('utf-8'))
  21.  
  22. key_list = ['alex','db','sb']
  23. for item in key_list:
  24. get_data(item)

  多线程解决并发:

  1. import threading
  2.  
  3. key_list = ['alex','db','sb']
  4. for item in key_list:
  5. t = threading.Thread(target=get_data,args=(item,))
  6. t.start()

  前面这几个程序在给发送连接请求时,必定会阻塞住,在哪儿等待百度给它回消息.我们可以把阻塞的地方变成非阻塞,这样可以一直给百度发送请求了,不要在哪儿傻傻的等待百度给回复了.

单线程的并发:

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

  什么是异步非阻塞?

    非阻塞,不等待

      比如创建socket对某个地址进行connect丶获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作,如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可

    异步,通知,执行完成之后自动执行回调函数或自动执行某些操作(通知).

      比如做爬虫中向某个地址baidu.com发送请求,当请求执行完成之后自执行回调函

三丶协程

  协程也可以称为"微线程",就是开发者控制线程执行流程,控制先执行某段代码然后再切换到另外函数执行代码,来回切换

  需要强调的是:

    1.Python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到IO或执行时间过长就会被迫交出CPU权限,切换其他线程运行)

    2.单线程内开启进程,一旦遇到IO,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非IO操作的切换与效率无关)

  对比操作系统控制线程的切换,用户在单线程内控制协程的切换

  优点如下:

    1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

    2.单线程内就可以实现并发的效果,最大限度地利用CPU

  缺点如下:

    1.协程的本质是单线程下,无法利用多核,可以使一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

    2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

  总结:

    1.必须在只有一个单线程里实现并发(协程本身无法实现并发)

    2.修改共享数据不需加锁

    3.用户程序里自己保存多个控制流的上下文栈

    4.附加一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield,greenlet都无法实现,就用到了gevent模块(select机制))

  Greenlet模块

  安装:pip3 install greenlet

  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.  
  19. gr1.switch()

 

  Gevent模块:

  安装:pip3 install gevent

  Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

  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多路复用丶基于IO多路复用+socket实现并发请求丶协程的更多相关文章

  1. Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程

    1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...

  2. 并发编程:协程TCP、非阻塞IO、多路复用、

    一.线程池实现阻塞IO 二.非阻塞IO模型 三.多路复用,降低CPU占用 四.模拟异步IO 一.线程池实现阻塞IO 线程阻塞IO 客户端 import socket c = socket.socket ...

  3. 网络编程进阶:并发编程之协程、IO模型

    协程: 基于单线程实现并发,即只用一个主线程(此时可利用的CPU只有一个)情况下实现并发: 并发的本质:切换+保存状态 CPU正在运行一个任务,会在两种情况下切走去执行其他任务(切换有操作系统强制控制 ...

  4. python全栈开发从入门到放弃之socket并发编程之协程

    一.为什么会有协程 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两种情 ...

  5. 协程IO多路复用

    协程:单线程下实现并发并发:伪并行,遇到IO就切换,单核下多个任务之间切换执行,给你的效果就是貌似你的几个程序在同时执行.提高效率任务切换 + 保存状态并行:多核cpu,真正的同时执行串行:一个任务执 ...

  6. Python IO 多路复用 \协程

    IO 多路复用 作用:  检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据) 即(可读/可写) IO请求时 解决并发  :  单线程 def get_data(key): cl ...

  7. IO多路复用、协程

    一.铺垫:基于socket发送http请求 1.需求一:向百度发送请求搜索关键字“alex”,有如下两种方式: import requests ret = requests.get('https:// ...

  8. 协程与IO多路复用

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

  9. 进击的Python【第十章】:Python的高级应用(多进程,进程间通信,协程与异步,牛逼的IO多路复用)

    Python的socket高级应用(多进程,协程与异步) 一.多进程multiprocessing multiprocessing is a package that supports spawnin ...

随机推荐

  1. sharepoint SDDL 字符串包含无效的SID或无法转换的SID

    安装过程中出现以下错误 采用独立模式安装Sharepoint Server 2013/Foundation 2013,在进行配置向导的时候会碰到这样的错误 System.ArgumentExcepti ...

  2. 树莓派 Learning 002 装机后的必要操作 --- 04 添加软件源 之 添加公钥 --- 解决“由于没有公钥,无法验证下列签名”问题

    树莓派 装机后的必要操作 - 添加软件源 解决 添加公钥 时会遇到的问题 当你添加完Debian的软件源后,在终端中执行sudo apt-get update时,会出现下面的错误:(这里我添加了3个软 ...

  3. 6.5 Ubuntu中安装搜狗输入法

    传统的方式:http://www.cnblogs.com/zlslch/p/6943318.html 最简单的方式:

  4. Struts2学习第七课 OGNL

    request变成了struts重写的StrutsRequestWrapper 关于值栈: helloWorld时,${productName}读取productName值,实际上该属性并不在requ ...

  5. 6.JBoss5.x6.x 反序列化漏洞(CVE-2017-12149)复现

    2017 年 9 月 14 日,国家信息安全漏洞共享平台( CNVD )收录了 JBOSS Application Server 反序列化命令执行漏洞( CNVD-2017-33724,对应 CVE- ...

  6. 7.21实习培训日志-JDBC JSP Servlet

    JDBC JSP Servlet 总结 今天早上的考试主要考Java的网络,HttpClient的get,post方法,Socket的文件传输和Xml和Json文件的解析,对于HttpCLient很简 ...

  7. Java web错误汇总

    环境: 电脑:win 10 IDE: Eclipse Java EE IDE for Web Developers. Version: Luna Service Release 2 (4.4.2) B ...

  8. elasticsearch 增删改流程和写一致性

    增删改流程: 1. 客户端和任一节点(假设 Node1)发出请求,这个node就是coordinating node(协调节点) 2. coordinating node,对document进行路由, ...

  9. 没有找零 状压dp

    没有找零 状压dp 约翰到商场购物,他的钱包里有K(1 <= K <= 16)个硬币,面值的范围是1..100,000,000.约翰想按顺序买 N个物品(1 <= N <= 1 ...

  10. RabbitMQ简介和使用

    一.RabbitMQ简介 1.什么是RabbitMQ AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设 ...