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 相关文章推荐
Python3中类、模块、错误与异常、文件的简易教程
Nov 20 Python
Python 装饰器实现DRY(不重复代码)原则
Mar 05 Python
matplotlib subplots 调整子图间矩的实例
May 25 Python
python定时按日期备份MySQL数据并压缩
Apr 19 Python
使用python实现简单五子棋游戏
Jun 18 Python
完美解决python3.7 pip升级 拒绝访问问题
Jul 12 Python
python opencv调用笔记本摄像头
Aug 28 Python
Python如何优雅获取本机IP方法
Nov 10 Python
python GUI库图形界面开发之PyQt5多线程中信号与槽的详细使用方法与实例
Mar 08 Python
Python实现清理微信僵尸粉功能示例【基于itchat模块】
May 29 Python
python爬虫中抓取指数的实例讲解
Dec 01 Python
Matplotlib可视化之添加让统计图变得简单易懂的注释
Jun 11 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 set_time_limit(0) 设置程序执行时间的函数
2010/05/26 PHP
PHP 处理TXT文件(打开/关闭/检查/读取)
2013/05/13 PHP
php输出echo、print、print_r、printf、sprintf、var_dump的区别比较
2013/06/21 PHP
解析php扩展php_curl.dll不加载的解决方法
2013/06/26 PHP
PHP图片处理之使用imagecopyresampled函数实现图片缩放例子
2014/11/19 PHP
php删除数组指定元素实现代码
2017/05/03 PHP
[原创]PHP global全局变量经典应用与注意事项分析【附$GLOBALS用法对比】
2019/07/12 PHP
PHP 代码简洁之道(小结)
2019/10/16 PHP
JavaScript 无符号右移运算符
2009/04/17 Javascript
JavaScript 验证浏览器是否支持javascript的方法小结
2009/05/17 Javascript
JavaScript具有类似Lambda表达式编程能力的代码(改进版)
2010/09/14 Javascript
raphael.js绘制中国地图 地图绘制方法
2014/02/12 Javascript
Node.js 异步编程之 Callback介绍(一)
2015/03/30 Javascript
使用vue.js实现checkbox的全选和多个的删除功能
2017/02/17 Javascript
微信小程序实现tab页面切换功能
2018/07/13 Javascript
react 父子组件之间通讯props
2018/09/08 Javascript
require.js 加载过程与使用方法介绍
2018/10/30 Javascript
JavaScript使用ul中li标签实现删除效果
2019/04/15 Javascript
小程序如何支持使用 async/await详解
2019/09/12 Javascript
JavaScript实现秒杀时钟倒计时
2019/09/29 Javascript
javascript设计模式 ? 命令模式原理与用法实例分析
2020/04/20 Javascript
js实现淘宝浏览商品放大镜功能
2020/10/28 Javascript
[02:32]【DOTA2亚洲邀请赛】iceice,梦开始的地方
2017/03/13 DOTA
Python实现公历(阳历)转农历(阴历)的方法示例
2017/08/22 Python
Scrapy抓取京东商品、豆瓣电影及代码分享
2017/11/23 Python
Python使用wxPython实现计算器
2018/01/30 Python
Python K最近邻从原理到实现的方法
2019/08/15 Python
Python图像处理模块ndimage用法实例分析
2019/09/05 Python
Anaconda之conda常用命令介绍(安装、更新、删除)
2019/10/06 Python
Python学习笔记之函数的参数和返回值的使用
2019/11/20 Python
Python colormap库的安装和使用详情
2020/10/06 Python
localStorage 设置过期时间的方法实现
2018/12/21 HTML / CSS
使用canvas对多图片拼合并导出图片的方法
2018/08/28 HTML / CSS
高中生自我评价个人范文
2013/11/09 职场文书
《一株紫丁香》教学反思
2014/02/19 职场文书
ElementUI实现el-form表单重置功能按钮
2021/07/21 Javascript