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

2020-03-15 22:51:18 +08:00
 lithbitren

最近试了试一个新的框架 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 里有没有可能解决,发出来大家讨论讨论。

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

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

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

另外一个很自然的问题是:你的 C 扩展使用的 C 运行时是 Python 的那一个,还是它有自己的 C 运行时?如果 C 扩展改变的是自己的运行时的设置,当然对 Python 不会有改变。
lithbitren
2020-03-16 14:26:57 +08:00
现在的绥靖方案是 Nginx 加多进程,也可以达到每秒 qps 过千,本机服务基本可以做到全 200,fastAPI 作为 python 框架还是挺快的,其他 py 框架在 cpython 上运行很难达到这个速度。
lithbitren
2020-03-16 14:32:37 +08:00
@geelaw 一开始确实是不知道,后来试过了就知道在 Python 程序里直接运行这个是没用的,还请大佬明示在哪设置可以作用到 Python 的运行上。
yulon
2020-03-16 14:46:26 +08:00
不管是 Win 还是 Linux,用 select 就是玩具,不要在玩具上浪费时间

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/653100

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX