设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间05 现在时间05 缓存数据 你是我永远无法触及的光

你是我永远无法触及的光 -- 孤单心事

查看: 975|回复: 2

Python解决asyncio文件描述符最大数量限制的问题

[复制链接]

  离线 

TA的专栏

  • 打卡等级:常驻代表
  • 打卡总天数:33
  • 打卡月天数:1
  • 打卡总奖励:364
  • 最近打卡:2025-07-01 21:04:06
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
268
主题
226
精华
0
金钱
1133
积分
564
注册时间
2023-1-26
最后登录
2025-7-1

发表于 2024-6-28 04:46:09 | 显示全部楼层 |阅读模式
目录


  • 问题复现
  • 问题分析

    • 事件循环 EventLoop
    • I/O 多路复用
    • select 的缺点

  • 解决方法

    • 1.更换事件循环选择器
    • 2.限制并发量
    • 3.修改最大文件描述符限制Windows

  • 总结

问题复现

Windows 平台下,Python 版本 3.5,使用异步框架 asyncio,有时候会出现 ValueError: too many file descriptors in select() 的报错信息,我们就来聊一下为什么会出现这种问题,以及问题的一些解决方法。
写一个小 dome 复现这个问题(环境:Windows 64 位、Python 3.7):
  1. import aiohttp
  2. import asyncio


  3. num = 0


  4. async def main(url):
  5.     async with aiohttp.ClientSession() as session:
  6.         async with session.get(url) as response:
  7.             global num
  8.             num += 1
  9.             print('%s ——> %s' % (str(num), response.status))


  10. def tasks():
  11.     url = 'https://www.baidu.com/s?ie=UTF-8&wd=%s'
  12.     task = [main(url % i) for i in range(10000)]
  13.     return task


  14. loop = asyncio.get_event_loop()
  15. loop.run_until_complete(asyncio.wait(tasks()))
复制代码
在打印 500 次左右后就会出现以下报错:
1.png


问题分析

好像这个报错和 select 有关,那什么是 select 呢?要怎么解决呢?别急,我们首先来了解一下 asyncio 中的事件循环,即 EventLoop。

事件循环 EventLoop

事件循环是 asyncio 的核心,异步任务的运行、任务完成之后的回调、网络 I/O 操作、子进程的运行,都是通过事件循环完成的,通俗来讲,事件循环所做的就是等待事件发生,然后再将每个事件与我们已明确与所述事件类型匹配的函数进行匹配。
下图很好的展示了协程、事件循环之间的相互作用:
2.png

在 asyncio 中,主要提供了两种不同事件循环的实现方法:

  • SelectorEventLoop:基于 selectors 模块的事件循环,selectors 又是建立在底层的 I/O 复用模块 select 之上的,selectors 提供了高度封装和高效的 I/O 复用,也就是说 SelectorEventLoop 在底层就是使用了 select I/O 多路复用的机制。
  • ProactorEventLoop:使用 IOCP 专为 Windows 构建的事件循环,IOCP 全称 I/O Completion Port,即 I/O 完成端口。它是支持多个同时发生的异步 I/O 操作的应用程序编程接口,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,是 Windows 下性能最好的 I/O 模型,有关 IOCP 的详细介绍可参考微软文档。
那么这两种方法有什么区别呢?在 asyncio 中什么时候用什么方法呢?
我们不妨看一下 asyncio 的源码,在 Python 3.7 中,无论在 Windows 还是 Linux 中都可以看到其默认的设置是 SelectorEventLoop:
3.png

我们也可以分别在 Windows 平台和 Linux 平台打印一下 EventLoop 对象(Python 3.7),可以看到默认都是 SelectorEventLoop:
  1. import asyncio

  2. loop = asyncio.get_event_loop()
  3. print(loop)
复制代码

  • Windows:
4.png


  • Linux:
5.png

事实上,在 Python 3.7 以及之前的版本中, 所有平台默认使用的都是 SelectorEventLoop,在 Python 3.8 以及以后的版本中,Unix 平台默认使用的是 SelectorEventLoop,Windows 平台默认使用的是 ProactorEventLoop,这个差异可以在官方文档中看到。

  • Python 3.7 文档:https://docs.python.org/3.7/library/asyncio-eventloop.html#event-loop-implementations
  • Python 3.8 文档:https://docs.python.org/3.8/library/asyncio-eventloop.html#event-loop-implementations
6.png

说了这么多,这和
  1. ValueError: too many file descriptors in select()
复制代码
的报错问题有什么关系呢?select 到底是什么东西呢?

I/O 多路复用

要了解 select,我们还要了解一下什么是 I/O 多路复用(I/O multiplexing),服务器端编程经常需要构造高性能的 I/O 模型,常见的 I/O 模型有同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用等;当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O 多路复用技术进行处理,I/O 多路复用技术就是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。
select,poll,epoll 等都是 I/O 多路复用的一种机制,其中后两个在 Linux 中可用,Windows 仅支持 select,I/O 多路复用通过这种机制,可以监视多个描述符,一旦某个描述符就绪,一般是读就绪或者写就绪,就是在这个文件描述符进行读写操作之前,能够通知程序进行相应的读写操作。

select 的缺点

I/O 多路复用这个概念被提出来以后, select 是第一个实现这个概念的,select 被实现以后,很快就暴露出了很多问题,其中一个缺点就是 select 在 Windows 中限制了文件描述符数量为 512 个,在 Linux 中限制为 1024 个,那么在前面的 dome 中,使用的是 Python 3.5,这个版本的 asyncio 默认使用了 SelectorEventLoop,底层调用的是 select,受 select 缺点的影响,并发量过高,就出现了 ValueError: too many file descriptors in select() 的报错信息。

解决方法


1.更换事件循环选择器

如果你使用的是 Python 3.7 及以下的版本,那么在 Windows 平台,可以使用 ProactorEventLoop。在 Linux 平台可以使用 PollSelector。
注意:如果你使用了 ProactorEventLoop,那么你将无法使用代理!这是 asyncio 的 bug,早在 2020 年 1 月就有人提过 issue,目前仍然可以看到类似的 issue,官方貌似也还没办法解决,所以,如果您必须要使用代理,则可以参考后面的解决办法。
  1. import selectors
  2. import asyncio
  3. import sys

  4. if sys.platform == 'win32':
  5.     loop = asyncio.ProactorEventLoop()
  6.     asyncio.set_event_loop(loop)
  7. else:
  8.     selector = selectors.PollSelector()
  9.     loop = asyncio.SelectorEventLoop(selector)
  10.     asyncio.set_event_loop(loop)
复制代码
2.限制并发量

可以使用方法
  1. asyncio.Semaphore()
复制代码
来限制并发量,Semaphore 就是信号量的意思,Semaphore 管理一个内部计数器,该计数器在每次调用
  1. acquire()
复制代码
方法时递减,每次调用
  1. release()
复制代码
方法时递增,计数器永远不会低于零,当方法
  1. acquire()
复制代码
发现它为零时,它会阻塞,等待其他线程调用
  1. release()
复制代码
方法。
通过限制并发量的方法来解决报错问题是个不错的选择。
  1. import aiohttp
  2. import asyncio


  3. num = 0


  4. async def main(url, semaphore):
  5.     async with semaphore:
  6.         async with aiohttp.ClientSession() as session:
  7.             async with session.get(url) as response:
  8.                 global num
  9.                 num += 1
  10.                 print('%s ——> %s' % (str(num), response.status))


  11. def tasks():
  12.     semaphore = asyncio.Semaphore(300)                         # 限制并发量为 300
  13.     url = 'https://www.baidu.com/s?ie=UTF-8&wd=%s'
  14.     task = [main(url % i, semaphore) for i in range(10000)]    # #总共 10000 任务
  15.     return task


  16. loop = asyncio.get_event_loop()
  17. loop.run_until_complete(asyncio.wait(tasks()))
复制代码
3.修改最大文件描述符限制Windows

在 Windows 中,最大文件描述符限制在 C 语言的头文件 Winsock2.h 中使用变量
  1. FD_SETSIZE
复制代码
进行定义,如果要修改它,可以通过在包含 Winsock2.h 之前将
  1. FD_SETSIZE
复制代码
定义为另一个值来修改,如果我们使用的编程语言是 Python 的话,是不太好对这个值进行修改的,可以参考微软官方文档:https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select
Linux
在 Linux 平台,可以使用
  1. ulimit
复制代码
命令来修改最大文件描述符限制:

  • 查看当前会话最大文件描述符限制(默认1024):
    1. ulimit -n
    复制代码
  • 临时修改限制,只对当前的会话有效:
    1. ulimit -SHn 65536
    复制代码
  • 永久修改限制,在
    1. /etc/security/limits.conf
    复制代码
    文件里新增以下内容:
  1. * hard nofile 65536
  2. * soft nofile 65536
复制代码
  1. ulimit
复制代码
命令参考:

  • -S使用软 (soft) 资源限制
  • -H使用硬 (hard) 资源限制
  • -a所有当前限制都被报告
  • -b套接字缓存尺寸
  • -c创建的核文件的最大尺寸
  • -d一个进程的数据区的最大尺寸
  • -e最高的调度优先级 (nice)
  • -f有 shell 及其子进程可以写的最大文件尺寸
  • -i最多的可以挂起的信号数
  • -k分配给此进程的最大 kqueue 数量
  • -l一个进程可以锁定的最大内存尺寸
  • -m最大的内存进驻尺寸
  • -n最多的打开的文件描述符个数
  • -p管道缓冲区尺寸
  • -qPOSIX 信息队列的最大字节数
  • -r实时调度的最大优先级
  • -s最大栈尺寸
  • -t最大的CPU时间,以秒为单位
  • -u最大用户进程数
  • -v虚拟内存尺寸
  • -x最大的文件锁数量
  • -P最大伪终端数量
  • -T最大线程数量

总结

asyncio 事件循环选择器,在 Python 3.7 以及之前的版本中,所有平台默认使用的都是 SelectorEventLoop,在 Python 3.8 以及以后的版本中,Unix 平台默认使用的是 SelectorEventLoop,Windows 平台默认使用的是 ProactorEventLoop。
select 在 Windows 中限制了文件描述符最大数量为 512 个,在 Linux 中限制为 1024 个。
要解决
  1. ValueError: too many file descriptors in select()
复制代码
的报错问题,根据您的平台和业务要求选择合理的解决方法:
Windows

  • 通过
    1. asyncio.Semaphore()
    复制代码
    方法来限制并发量,通常设置在 300-500 比较合理,这是最优的做法;
  • 更换 asyncio 的事件循环选择器为 ProactorEventLoop,注意:这将导致无法使用代理!
Linux

  • 通过
    1. asyncio.Semaphore()
    复制代码
    方法来限制并发量,通常设置在 800-1000 比较合理;
  • 通过
    1. ulimit
    复制代码
    命令来修改最大文件描述符限制;
  • 更换 asyncio 的事件循环选择器为 PollSelector。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持晓枫资讯。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
22
积分
24
注册时间
2022-12-27
最后登录
2022-12-27

发表于 6 天前 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
16
积分
12
注册时间
2022-12-28
最后登录
2022-12-28

发表于 3 天前 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表