python 并发编程 非阻塞IO模型原理解析


Posted in Python onAugust 20, 2019

非阻塞IO(non-blocking IO)

Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

python 并发编程 非阻塞IO模型原理解析

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的,这段是本地拷贝,copy data ),然后返回。

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,
此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,
循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,
进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel操作系统内存 数据准备好了没有。

非阻塞IO示例

  • 设置socket接口为 非阻塞IO接口
  • 默认是True 为阻塞
  • server.setblocking(False)
  • 处理一下这个异常

BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。

from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 设置socket接口为 非阻塞IO接口
# 默认是True 为阻塞
server.setblocking(False)
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    print(addr)

  except BlockingIOError:
    print("干其他的工作")
server.close()

执行结果,如上面的图,一直返回error消息

starting...
干其他的工作
干其他的工作
干其他的工作
干其他的工作

服务端 可以与 多个客户端建立连接,实现服务端可以不停的建立连接

from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 设置socket接口为 非阻塞IO接口
# 默认是True 为阻塞
server.setblocking(False)
r_list = []
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    r_list.append(conn)
    print(r_list)
  except BlockingIOError:
    pass
server.close()

起三个客户端与服务端建立连接

python 并发编程 非阻塞IO模型原理解析

r_list 存着所有建立的连接

有连接来,就建立连接,没有连接来,就抛出异常

实现IO非阻塞 并发 多个连接

from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 设置socket接口为 非阻塞IO接口
# 默认是True 为阻塞
server.setblocking(False)
r_list = []
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    r_list.append(conn)
    print(r_list)
  except BlockingIOError:
    # 定义删除连接列表
    del_rlist = []
    for conn in r_list:
      try:
        data = conn.recv(1024)
        # 收空数据时候
        if not data:
          del_rlist.append(conn)
          continue
        conn.send(data.upper())
      # 没有连接,抛出异常,就结束这次循环,继续
      except BlockingIOError:
        continue
      # 套接字出现异常,客户端单方面连接断开
      except Exception:
        conn.close()
        del_rlist.append(conn)
        break
    # 结束上面循环之后,循环del_list 连接元素 删除连接
    for conn in del_rlist:
      del_rlist.remove(conn)
server.close()

BUG:send也是IO阻塞接口

当send在数据量过大时候,也会阻塞。

send操作是,把应用程序把数据发送到操作系统缓存区里,而操作系统缓存区空间也是有限的。缓存区也会满了,后面还有数据需要发送,那只能等缓存区清掉数据,有空间了,才能发送数据。所以在这里缓存区满了,就阻塞。

修改后服务端的代码 可以自己检测IO,遇到IO切换单个线程的其他任务,去运行,实现单线程并发

from socket import *
server = socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8000))
server.listen(5)
# 设置socket接口为 非阻塞IO接口
# 默认是True 为阻塞
server.setblocking(False)
r_list = []
w_list = []
print("starting...")
while True:
  try:
    conn,addr = server.accept()
    r_list.append(conn)
    print(r_list)
  except BlockingIOError:
    # 收消息
    # 定义删除连接列表
    del_rlist = []
    for conn in r_list:
      try:
        data = conn.recv(1024)
        # 收空数据时候
        if not data:
          del_rlist.append(conn)
          continue
        '''加入元祖 元祖有两个元素 
        1.存放套接字连接
        2.准备要发送的的数据
        '''
        w_list.append((conn, data.upper()))
      # 没有连接,抛出异常,就结束这次循环,继续
      except BlockingIOError:
        continue
      # 套接字出现异常,客户端单方面连接断开
      except Exception:
        conn.close()
        del_rlist.append(conn)
        break
    # 发消息
    # 用于 发成功数据后,删除套接字连接的列表
    del_wlist = []
        for item in w_list:
     try:
        conn = item[0]
        data = item[1]
        conn.send(data)
        # 发成功后,从列表删除连接
        del_wlist.append(item)
      # send 有可能出现异常 没发完情况
      except BlockingIOError:
        pass
    # 结束上面循环之后,循环del_wlist 连接元素 删除连接
    for item in del_wlist:
      del_wlist.remove(item)
    # 结束上面循环之后,循环del_rlist 连接元素 删除连接
    for conn in del_rlist:
      del_rlist.remove(conn)
server.close()

这就是非阻塞IO

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

干其他活时候,有可能来新的连接,新的连接来了,不能及时响应与该新的连接,建立连接。所以会导致问题:数据不会及时响应

但是也难掩其缺点:

1. 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况

2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。
这会导致整体数据吞吐量的降低。

3.死循环While True会导致CPU的无用的耗用、占用

此外,在这个方案中recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如select()多路复用模式,可以一次检测多个连接是否活跃

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
使用PYTHON接收多播数据的代码
Mar 01 Python
讲解python参数和作用域的使用
Nov 01 Python
Python中使用item()方法遍历字典的例子
Aug 26 Python
Python使用metaclass实现Singleton模式的方法
May 05 Python
开源Web应用框架Django图文教程
Mar 09 Python
Python的装饰器使用详解
Jun 26 Python
numpy.random模块用法总结
May 27 Python
Python 的字典(Dict)是如何存储的
Jul 05 Python
PyCharm License Activation激活码失效问题的解决方法(图文详解)
Mar 12 Python
Python小白学习爬虫常用请求报头
Jun 03 Python
TensorFlow2.0使用keras训练模型的实现
Feb 20 Python
安装pytorch时报sslerror错误的解决方案
May 17 Python
Python实现某论坛自动签到功能
Aug 20 #Python
python函数的作用域及关键字详解
Aug 20 #Python
pytorch 可视化feature map的示例代码
Aug 20 #Python
python爬虫 基于requests模块的get请求实现详解
Aug 20 #Python
python爬虫 urllib模块url编码处理详解
Aug 20 #Python
pytorch实现用Resnet提取特征并保存为txt文件的方法
Aug 20 #Python
python web框架 django wsgi原理解析
Aug 20 #Python
You might like
PHP开发框架总结收藏
2008/04/24 PHP
php微信开发之上传临时素材
2016/06/24 PHP
PHP+MariaDB数据库操作基本技巧备忘总结
2018/05/21 PHP
JS模拟多线程
2007/02/07 Javascript
JAVASCRIPT实现的WEB页面跳转以及页面间传值方法
2010/05/13 Javascript
Javascript下IE与Firefox下的差异兼容写法总结
2010/06/18 Javascript
快速排序 php与javascript的不同之处
2011/02/22 Javascript
了不起的node.js读书笔记之例程分析
2014/12/22 Javascript
快速移动鼠标触发问题及解决方法(ECharts外部调用保存为图片操作及工作流接线mouseenter和mouseleave)
2016/08/29 Javascript
JavaScript中Hoisting详解 (变量提升与函数声明提升)
2017/08/18 Javascript
Vue Socket.io源码解读
2018/02/07 Javascript
JavaScript对象拷贝与赋值操作实例分析
2018/12/10 Javascript
vue-cli配置全局sass、less变量的方法
2019/06/06 Javascript
Layer+Echarts构建弹出层折线图的方法
2019/09/25 Javascript
在Vue中使用Echarts实例图的方法实例
2020/10/10 Javascript
[49:18]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 OG vs TNC
2018/04/01 DOTA
[51:14]LGD vs VP 2018国际邀请赛淘汰赛BO3 第一场 8.21
2018/08/22 DOTA
python转换字符串为摩尔斯电码的方法
2015/07/06 Python
使用django-guardian实现django-admin的行级权限控制的方法
2018/10/30 Python
Window环境下Scrapy开发环境搭建
2018/11/18 Python
Python第三方库h5py_读取mat文件并显示值的方法
2019/02/08 Python
Python Pandas分组聚合的实现方法
2019/07/02 Python
Python模块 _winreg操作注册表
2020/02/05 Python
Python读取Excel一列并计算所有对象出现次数的方法
2020/09/04 Python
师范应届生求职信
2013/11/15 职场文书
心得体会开头
2014/01/01 职场文书
总经理助理岗位职责范本
2014/07/20 职场文书
三问三解心得体会
2014/09/05 职场文书
一份教室追逐打闹的检讨书
2014/09/27 职场文书
给老婆的保证书
2015/01/16 职场文书
幼儿园中秋节活动总结
2015/03/23 职场文书
导游词之天津盘山
2019/11/01 职场文书
导游词之塘栖古镇
2019/12/04 职场文书
B站评分公认最好看的动漫,你的名字评分9.9,第六备受喜欢
2022/03/18 日漫
大脑的记忆过程在做数据压缩,不同图形也有共同的记忆格式
2022/04/29 数码科技
Python+Pillow+Pytesseract实现验证码识别
2022/05/11 Python