V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lithbitren
V2EX  ›  Python

windows 下 select.select()只能打开 500 出头个文件怎么解决?

  •  
  •   lithbitren · 2020-03-15 22:51:18 +08:00 · 6888 次点击
    这是一个创建于 1720 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近试了试一个新的框架 fastAPI,传说可做到到万级的 qps,想先在 win10 上试试,本来以未比不上 linux 应该还凑合吧,跟着教程搭起来了,啥都不说先来个 hello world 吧,跟 flask 本质上区别不大。

    from fastapi import FastAPI
    
    app = FastAPI()
    
    @app.get("/")
    def read_root():
        return "Hello World"
    

    结果并发一打就死了,秒内并发超过 510 直接就崩了,崩的非常稳定,一超 510 必崩,一次都不含糊。

    错误信息如下:

    Process SpawnProcess-1: Traceback (most recent call last): File "d:\python37\lib\multiprocessing\process.py", line 297, in _bootstrap self.run() File "d:\python37\lib\multiprocessing\process.py", line 99, in run self._target(*self._args, **self._kwargs) File "d:\python37\lib\site-packages\uvicorn\subprocess.py", line 61, in subprocess_started target(sockets=sockets) File "d:\python37\lib\site-packages\uvicorn\main.py", line 382, in run loop.run_until_complete(self.serve(sockets=sockets)) File "d:\python37\lib\asyncio\base_events.py", line 566, in run_until_complete self.run_forever() File "d:\python37\lib\asyncio\base_events.py", line 534, in run_forever self._run_once() File "d:\python37\lib\asyncio\base_events.py", line 1735, in _run_once event_list = self._selector.select(timeout) File "d:\python37\lib\selectors.py", line 323, in select r, w, _ = self._select(self._readers, self._writers, [], timeout) File "d:\python37\lib\selectors.py", line 314, in _select r, w, x = select.select(r, w, w, timeout) ValueError: too many file descriptors in select()
    

    重点就是最后一句。

    # ValueError: too many file descriptors in select()错误和解决

    上面说 “因为 asyncio 内部用到了 select,而 select 就是系统打开文件数是有限度的,这个其实是操作系统的限制,linux 打开文件的最大数默认是 1024,windows 默认是 509,超过了这个值,程序就开始报错, 下面的代码一次性将处理 url 的函数作为任务扔进了一个超大的 List 中,这就引起了错误。”

    上面的解决方法是用 asyncio.Semaphore(500)限制协程并发数,但说的是爬虫,服务端直接这样设定是没用的,StackOverflow 大多说的也是这个。

    python 异步编程之 asyncio (百万并发)

    "假如你的并发达到 2000 个,程序会报错:ValueError: too many file descriptors in select()。报错的原因字面上看是 Python 调取的 select 对打开的文件有最大数量的限制,这个其实是操作系统的限制,linux 打开文件的最大数默认是 1024,windows 默认是 509,超过了这个值,程序就开始报错。这里我们有三种方法解决这个问题:

    1.限制并发数量。(一次不要塞那么多任务,或者限制最大并发数量)

    2.使用回调的方式。

    3.修改操作系统打开文件数的最大限制,在系统里有个配置文件可以修改默认值,具体步骤不再说明了。

    不修改系统默认配置的话,个人推荐限制并发数的方法,设置并发数为 500,处理速度更快。"

    其中第三点说可以改配置,我搜了半天也找不到这配置在哪改,网上有几个提到改注册表的,但试过了都没用,而且应该不是一回事。

    系统进程打开文件最大句柄数的限制

    根据最大打开文件数,又找到了一个 c 的解决方案,于是我试着引了一个 c 扩展执行,想着就觉得没用,事实也没用。

    c:

    void set_max_stdio()
    {
        printf("%d \n", _getmaxstdio()); // 默认输出 512
        _setmaxstdio(2048); // 如果设定值大于 2048,就会变回 512
        printf("setmaxstdio: %d \n", _getmaxstdio()); // 正常会输出 2048
    }
    

    python:

    from ctypes import cdll
    cdll.LoadLibrary("./setmaxstdio.so").set_max_stdio()
    # 会正常打印 2048,但对并发限制没有影响
    

    Google,GitHub 和 StackOverflow 也找不到解决方案,当然大概率是因为英语太菜,找不到关联信息,

    所以问问大佬这个 select 的问题在 win10 里有没有可能解决,发出来大家讨论讨论。

    第 1 条附言  ·  2020-03-16 19:47:22 +08:00

    谢谢大佬们的积极回应,学习了很多。

    set_event_loop(ProactorEventLoop())
    

    可以解决select报错的问题。

    不说了,我开Linux去了。

    53 条回复    2020-03-20 17:23:04 +08:00
    lithbitren
        1
    lithbitren  
    OP
       2020-03-15 22:53:02 +08:00
    服务端的代码粘错了,函数前面少了个 async,不过测起来都是一样的。
    ClericPy
        2
    ClericPy  
       2020-03-15 23:00:04 +08:00
    linux 上经常遇到超过最大 fds 数量, 得改内核配置 ulimit 什么的

    Windows... 何必要用这玩意做生产环境呢, 搜搜 Windows 怎么提高默认最大 file handles 或者 file descriptors 吧... 老早以前还真找过, 忘了什么结果了, 最后被友军劝退别用 Windows
    lithbitren
        3
    lithbitren  
    OP
       2020-03-15 23:06:45 +08:00
    @ClericPy 不是生产环境,就是当玩具试试而已,开 windows server 被劝退太正常了 [捂脸]
    zhuangzhuang1988
        4
    zhuangzhuang1988  
       2020-03-15 23:31:00 +08:00
    用 uvloop 试试呗,
    本身 select 就不生产环境的
    unixeno
        5
    unixeno  
       2020-03-16 01:14:21 +08:00 via Android
    Windows 可能得换 iocp ?
    geelaw
        6
    geelaw  
       2020-03-16 05:15:23 +08:00 via iPhone
    为什么不搜索一下呢?

    https://stackoverflow.com/questions/870173/is-there-a-limit-on-number-of-open-files-in-windows

    C 运行时默认会限制最多同时有 512 个流式访问的 FILE,以及最多同时有 8192 个文件描述符,超过该限制则需要使用 Win32 CreateFile 系列 API,而一个进程可以同时使用的文件句柄数量的最大值至少是 16384。
    wwqgtxx
        7
    wwqgtxx  
       2020-03-16 06:27:55 +08:00 via iPhone
    自己把 asyncio 的 loop 换成 ProactorEventLoop 的即可,或者升级到 python3.8 默认就是这个
    楼上说 uvloop 的难道不知道 uvloop 根本就不支持 windows 么…
    wwqgtxx
        8
    wwqgtxx  
       2020-03-16 06:33:17 +08:00 via iPhone
    当然,还有些比较麻烦的办法解决(我自己干过)
    用 ctypes/cffi 加载一个 c 库 wepoll ( github 上有源码,可以自行修改后编译成 dll )
    然后基于 wepoll 的 api 写一个 Selector ( selector 包源代码里有 epoll 的 selector,可以拷贝出来,让其调用 wepoll ),在加载 asyncio 前设置为默认 Selector
    Trim21
        9
    Trim21  
       2020-03-16 07:20:50 +08:00 via Android
    试试 uvicorn 的限制并发的参数?
    Mithril
        10
    Mithril  
       2020-03-16 09:45:02 +08:00   ❤️ 3
    拿为 Linux 设计的东西往 Windows 上套,然后怪 Windows 垃圾。。。
    lithbitren
        11
    lithbitren  
    OP
       2020-03-16 14:07:10 +08:00
    @geelaw 为什么不好好看一下主楼的帖子? stackoverflow 这帖子我早看了,问题是发现了,但没有可靠的解决方案,我写了 c 扩展不行,后面在 pywin32 模块里面也找到了这个函数也没用。
    lithbitren
        12
    lithbitren  
    OP
       2020-03-16 14:09:15 +08:00
    @wwqgtxx 这个我之前也试过了。
    from asyncio import ProactorEventLoop, get_event_loop
    from uvicorn import Config, Server, run
    loop = ProactorEventLoop()
    config = Config(app=app, loop=loop)
    server = Server(config=config)
    loop = get_event_loop()
    loop.run_until_complete(server.serve())
    没用,我的环境是 3.7,升到 3.8 有一堆库用不了,于是又降回来了,迟早要升的,不过不是现在。
    lithbitren
        13
    lithbitren  
    OP
       2020-03-16 14:09:53 +08:00
    @Trim21 不太懂,设了 ProactorEventLoop 似乎没用
    lithbitren
        14
    lithbitren  
    OP
       2020-03-16 14:12:04 +08:00
    @Mithril 手头上只有 win10,我也明白大部分服务都是为 linux 设计的,但我上 fastAPI 的官网明明也是说支持 win 的,我按照教程完整走了一遍,其他都没啥大问题,就是这个并发数在 win 上不行,我在 GitHub 的官方社区问过,contributor 也没能解决这个问题。
    CallMeReznov
        15
    CallMeReznov  
       2020-03-16 14:17:19 +08:00
    LINUX 可以修改这个参数,可以修改到 1024 或者更多.
    但 WINDOWS 好像就没有办法了,就是 512.
    我之前尝试在 windows 上用 haproxy,就因为这个限制了并发而放弃,后来也没有深究.
    lithbitren
        16
    lithbitren  
    OP
       2020-03-16 14:18:31 +08:00
    @wwqgtxx wepoll 我研究研究看看,c 我只会写算法,底层太菜了。
    geelaw
        17
    geelaw  
       2020-03-16 14:23:20 +08:00
    @lithbitren #11 在 #6 暗示的答案就是使用 CreateFile 系列 API 而不是用 C 运行时的文件。你的贴子并没有表现出你知道 CreateFile 限制更少。

    另外一个很自然的问题是:你的 C 扩展使用的 C 运行时是 Python 的那一个,还是它有自己的 C 运行时?如果 C 扩展改变的是自己的运行时的设置,当然对 Python 不会有改变。
    lithbitren
        18
    lithbitren  
    OP
       2020-03-16 14:26:57 +08:00
    现在的绥靖方案是 Nginx 加多进程,也可以达到每秒 qps 过千,本机服务基本可以做到全 200,fastAPI 作为 python 框架还是挺快的,其他 py 框架在 cpython 上运行很难达到这个速度。
    lithbitren
        19
    lithbitren  
    OP
       2020-03-16 14:32:37 +08:00
    @geelaw 一开始确实是不知道,后来试过了就知道在 Python 程序里直接运行这个是没用的,还请大佬明示在哪设置可以作用到 Python 的运行上。
    yulon
        20
    yulon  
       2020-03-16 14:46:26 +08:00   ❤️ 1
    不管是 Win 还是 Linux,用 select 就是玩具,不要在玩具上浪费时间
    ysc3839
        21
    ysc3839  
       2020-03-16 15:01:35 +08:00
    @lithbitren #12 所以你改用 ProactorEventLoop 之后遇到了什么问题?错误信息?
    lithbitren
        22
    lithbitren  
    OP
       2020-03-16 15:03:11 +08:00
    @ysc3839 就是并发高的时候还是主楼那个 select 错误一模一样的,没有其他错误信息。
    ysc3839
        23
    ysc3839  
       2020-03-16 15:04:43 +08:00
    @lithbitren 一模一样的话说明还用的是 select 吧?
    lithbitren
        24
    lithbitren  
    OP
       2020-03-16 15:06:47 +08:00
    @ysc3839 嗯,我也不懂怎么不用 select,可能是框架的问题,这框架我才上手一天多,源码还没下决心去看。。
    Mithril
        25
    Mithril  
       2020-03-16 17:47:20 +08:00
    @lithbitren Windows 和 Linux 用来支持高性能 IO 的接口设计模式是不一样的。所以很多库没法同时支持这两个系统,或者同时在这两个系统上达到最高的性能。顶多就是两边都能用而已。用一种模式模拟另一种总会有很多的妥协。
    loqixh
        26
    loqixh  
       2020-03-16 17:50:55 +08:00 via Android
    @wwqgtxx win 可以用 uv,性能很好的
    wwqgtxx
        27
    wwqgtxx  
       2020-03-16 18:39:47 +08:00 via iPhone
    @loqixh 我说的是 uvloop,你确定 uvloop 支持 win 了么,还是说你说的是 libuv ?
    https://github.com/MagicStack/uvloop/issues/14
    wwqgtxx
        28
    wwqgtxx  
       2020-03-16 18:42:09 +08:00 via iPhone
    @lithbitren 按说如果你改用 ProatorEventLoop 之后还出现一样的问题肯定是修改 loop 没生效你再好好的检查你的代码吧
    lithbitren
        29
    lithbitren  
    OP
       2020-03-16 19:19:52 +08:00
    @wwqgtxx

    按官方教程是用命令行启动 uvicorn 的,后来去了 Github 问了,考虑到要改东西,所以改成用 if main 启动了,测试过其他状态都正常,仍然是只有并发数顶死 510,就这么简单的代码,我也不晓得该咋改了。

    ====main.py====

    from asyncio import ProactorEventLoop, get_event_loop
    from uvicorn import Config, Server
    from fastapi import FastAPI

    app = FastAPI()

    @app.get('/')
    async def index_post():
    ㅤreturn 'hello world'

    if __name__ == "__main__":
    ㅤserver = Server(
    ㅤㅤconfig=Config(
    ㅤㅤㅤapp=app,
    ㅤㅤㅤloop=ProactorEventLoop()
    ㅤㅤ)
    ㅤ)
    ㅤget_event_loop().run_until_complete(
    ㅤㅤserver.serve()
    ㅤ)
    lithbitren
        30
    lithbitren  
    OP
       2020-03-16 19:23:55 +08:00
    @wwqgtxx 这个方法是 Github 的 contributor 教的,最后他说 honestly I dont know 了。
    black11black
        31
    black11black  
       2020-03-16 19:31:44 +08:00
    你不了解多路复用机制在这里问这些别人很难回答你...
    所有声称异步框架性能测试 100%基于 linux,你一定说我就要 windows,那答案就是简单的没办法。select 由于轮寻机制即时解锁 fd 上限性能一样会挂,win 下 iocp 在 py 内目前没有生产级封装。
    Qzier
        32
    Qzier  
       2020-03-16 19:36:41 +08:00   ❤️ 1
    if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
    else:
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    Qzier
        33
    Qzier  
       2020-03-16 19:37:10 +08:00   ❤️ 1
    lithbitren
        34
    lithbitren  
    OP
       2020-03-16 19:43:40 +08:00
    @Qzier 谢谢大佬指点,qps 虽然上不去,但不会崩了。
    lithbitren
        35
    lithbitren  
    OP
       2020-03-16 19:45:13 +08:00
    @black11black 嗯啊,是不太了解机制,只会一些应用,没有说一定要用 win10,只是碰到了问题,搜不出答案,所以拿出来讨论讨论,学习一下。
    v2eeeeee
        36
    v2eeeeee  
       2020-03-16 19:53:08 +08:00
    请教下大佬们
    网络流的高并发,单机做到万级的 qps,是因为网络流由 "有限的进程" 维护对吗?
    (不是一个进程维护一个网络流这样?如果是那样,万级 qps 不得有万级进程来维护? linux 的 max user processes 也吃不消吧
    lithbitren
        37
    lithbitren  
    OP
       2020-03-16 20:12:24 +08:00
    @Qzier ...不好意思,我傻-逼了,刚刚可能是卡了,访问功率低了,重启了以后访问功率超过 510,还是不行,还是会爆 select 错。。。
    black11black
        38
    black11black  
       2020-03-16 20:12:53 +08:00
    @lithbitren 你不了解机制,最好的解决办法就是装 linux,windows 的中断嵌套机制与 linux 各有取舍,就不是为网络服务设计的。装了 linux 你随意使用 epoll,uvloop 随便装,哪像 windows 这样缩手缩脚。系统级事务切换耗时的数量级也不一样。之前有过测试,自建事件循环,同样的 py 代码,即时只封装生成器做简单的事务切换,win 与 linux 的耗时表现也是完全不同
    lithbitren
        39
    lithbitren  
    OP
       2020-03-16 20:16:53 +08:00
    @Qzier 没事了没事,是我搞错了,还是可以的,我傻-逼*2
    lithbitren
        40
    lithbitren  
    OP
       2020-03-16 20:17:34 +08:00
    @black11black 嗯,说的有道理,还是 Linux 做服务稳定,遇到问题也好解决。
    janxin
        41
    janxin  
       2020-03-16 20:43:02 +08:00
    Windows 是事件循环实现的问题导致的,测试需要在 Linux 下进行,Mac 下的性能也不如 Linux
    janxin
        42
    janxin  
       2020-03-16 20:46:17 +08:00   ❤️ 1
    进程资源消耗比线程要重很多,所以不会是一个进程对应一个连接。

    这个问题过于广,建议还是看一下比较详细的描述
    http://www.kegel.com/c10k.html
    v2eeeeee
        43
    v2eeeeee  
       2020-03-16 20:52:42 +08:00
    @janxin 谢谢你!线程按说应该也是有个上限,很好奇万级 qps 的压测发起段是怎么做到的
    roundgis
        44
    roundgis  
       2020-03-17 00:30:55 +08:00
    windows 有个宏叫 FD_SETSIZE cpython 设置为 512, 你可以手动改大一些,例如 16384 之类就可以支持更大的并发了

    当然 windows 最好的还是用 IOCP
    lithbitren
        45
    lithbitren  
    OP
       2020-03-17 01:28:24 +08:00
    @roundgis FD_SETSIZE 怎么改啊,大佬明示。
    ysc3839
        46
    ysc3839  
       2020-03-17 02:10:13 +08:00
    @lithbitren 写在系统头文件里面的,改了要重新编译。
    msg7086
        47
    msg7086  
       2020-03-17 04:54:30 +08:00
    三个系统的事件接口都不一样,Windows 下是 IOCP,Linux 下是 epoll,BSD 下是 kqueue,生产环境下肯定要看是否支持这个系统的 API。如果你的运行环境只支持 epoll,那 Windows 和 BSD 下就只能降级运行了。
    loqixh
        48
    loqixh  
       2020-03-17 11:36:07 +08:00 via Android
    @wwqgtxx 汗,不好意识,我搞错了,我以为说的是 libuv 里的 uvloop 函数
    roundgis
        49
    roundgis  
       2020-03-17 14:48:04 +08:00
    @lithbitren cpython 我记得没错 select module 重新编译就好了
    lithbitren
        50
    lithbitren  
    OP
       2020-03-17 15:03:20 +08:00
    @roundgis 我也不是想当伸手党,不过重新编译 cpython 这真不太懂,也找不到清晰的教程,select 模块我也找不到在 python 根目录的对应文件,怕乱搞搞崩了。。
    lithbitren
        51
    lithbitren  
    OP
       2020-03-18 17:23:45 +08:00
    确认了一下,uvicorn 在参数设置里可以把 loop 设置为 uvloop 和 iocp,不过这两个选项在 windows 里都不支持,只支持 auto 的 asyncio。
    ruanimal
        52
    ruanimal  
       2020-03-20 15:04:05 +08:00
    lz 探索操作系统的精神值得肯定,学习框架的效率就不行了
    lithbitren
        53
    lithbitren  
    OP
       2020-03-20 17:23:04 +08:00
    @ruanimal 过完了一遍才测性能的,web 框架大体都是相通的,过一遍文档了解 API 的存缺情况就差不多了,而且一般也不会拿纯 win 做生产环境,这种问题在框架里一般也很难碰上。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2816 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 03:59 · PVG 11:59 · LAX 19:59 · JFK 22:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.