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 里有没有可能解决,发出来大家讨论讨论。

6631 次点击
所在节点    Python
53 条回复
ysc3839
2020-03-16 15:01:35 +08:00
@lithbitren #12 所以你改用 ProactorEventLoop 之后遇到了什么问题?错误信息?
lithbitren
2020-03-16 15:03:11 +08:00
@ysc3839 就是并发高的时候还是主楼那个 select 错误一模一样的,没有其他错误信息。
ysc3839
2020-03-16 15:04:43 +08:00
@lithbitren 一模一样的话说明还用的是 select 吧?
lithbitren
2020-03-16 15:06:47 +08:00
@ysc3839 嗯,我也不懂怎么不用 select,可能是框架的问题,这框架我才上手一天多,源码还没下决心去看。。
Mithril
2020-03-16 17:47:20 +08:00
@lithbitren Windows 和 Linux 用来支持高性能 IO 的接口设计模式是不一样的。所以很多库没法同时支持这两个系统,或者同时在这两个系统上达到最高的性能。顶多就是两边都能用而已。用一种模式模拟另一种总会有很多的妥协。
loqixh
2020-03-16 17:50:55 +08:00
@wwqgtxx win 可以用 uv,性能很好的
wwqgtxx
2020-03-16 18:39:47 +08:00
@loqixh 我说的是 uvloop,你确定 uvloop 支持 win 了么,还是说你说的是 libuv ?
https://github.com/MagicStack/uvloop/issues/14
wwqgtxx
2020-03-16 18:42:09 +08:00
@lithbitren 按说如果你改用 ProatorEventLoop 之后还出现一样的问题肯定是修改 loop 没生效你再好好的检查你的代码吧
lithbitren
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
2020-03-16 19:23:55 +08:00
@wwqgtxx 这个方法是 Github 的 contributor 教的,最后他说 honestly I dont know 了。
black11black
2020-03-16 19:31:44 +08:00
你不了解多路复用机制在这里问这些别人很难回答你...
所有声称异步框架性能测试 100%基于 linux,你一定说我就要 windows,那答案就是简单的没办法。select 由于轮寻机制即时解锁 fd 上限性能一样会挂,win 下 iocp 在 py 内目前没有生产级封装。
Qzier
2020-03-16 19:36:41 +08:00
if sys.platform == 'win32':
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
else:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
Qzier
2020-03-16 19:37:10 +08:00
lithbitren
2020-03-16 19:43:40 +08:00
@Qzier 谢谢大佬指点,qps 虽然上不去,但不会崩了。
lithbitren
2020-03-16 19:45:13 +08:00
@black11black 嗯啊,是不太了解机制,只会一些应用,没有说一定要用 win10,只是碰到了问题,搜不出答案,所以拿出来讨论讨论,学习一下。
v2eeeeee
2020-03-16 19:53:08 +08:00
请教下大佬们
网络流的高并发,单机做到万级的 qps,是因为网络流由 "有限的进程" 维护对吗?
(不是一个进程维护一个网络流这样?如果是那样,万级 qps 不得有万级进程来维护? linux 的 max user processes 也吃不消吧
lithbitren
2020-03-16 20:12:24 +08:00
@Qzier ...不好意思,我傻-逼了,刚刚可能是卡了,访问功率低了,重启了以后访问功率超过 510,还是不行,还是会爆 select 错。。。
black11black
2020-03-16 20:12:53 +08:00
@lithbitren 你不了解机制,最好的解决办法就是装 linux,windows 的中断嵌套机制与 linux 各有取舍,就不是为网络服务设计的。装了 linux 你随意使用 epoll,uvloop 随便装,哪像 windows 这样缩手缩脚。系统级事务切换耗时的数量级也不一样。之前有过测试,自建事件循环,同样的 py 代码,即时只封装生成器做简单的事务切换,win 与 linux 的耗时表现也是完全不同
lithbitren
2020-03-16 20:16:53 +08:00
@Qzier 没事了没事,是我搞错了,还是可以的,我傻-逼*2
lithbitren
2020-03-16 20:17:34 +08:00
@black11black 嗯,说的有道理,还是 Linux 做服务稳定,遇到问题也好解决。

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

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

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

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

© 2021 V2EX