V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
css3
V2EX  ›  程序员

python3 多进程求助 OSError: [Errno 24] Too many open files

  •  
  •   css3 · 301 天前 · 2448 次点击
    这是一个创建于 301 天前的主题,其中的信息可能已经有所发展或是发生改变。

    业务:有大量(本次测试时 1 万多张)图片需要转成 base64 编码后,送入 http 接口请求处理,我采用以下代码: base64 用生成器处理, request 用多进程。 但下面代码跑到一半的时候,直接抛了 OSError: [Errno 24] Too many open files, 百度了一下,看上去是进程超过所能开启的最大文件数了, ulimit -n # mac 8192 请教下各位,我怎么应该 fix 这个问题,最终需求就是想快速高效的完成这个操作,可能我写的代码一开始就有问题,还希望大佬们指点一下。

    import json
    import time
    import requests
    import base64
    import os
    from multiprocessing import Process
    
    
    
    
    def img_to_base64(img_path):
        r = {}
        for root, dirs, files in os.walk(img_path):
            for pic in files:
                if pic.endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif')):
                    img = os.path.join(root, pic)
                    with open(img, 'rb') as f:
                        bs64 = base64.b64encode(f.read()).decode('utf-8')
                        r[img] = bs64
                        yield r
    
    
    
    def req(host, img_path):
        bs64_generator = img_to_base64(img_path)
        procs = []
    
        for items in bs64_generator:
            body, pic = None, None
            for pic, base64 in items.items():
                body = {
                    "requests": [
                        {
                            "resource": {
                                "base64": base64
                            }
                        }
                    ]
                }
    
            p = Process(target=r, args=(host, body, pic))
            procs.append(p)
            p.start()
        for proc in procs:
            proc.join()
    
    
    
    
    def r(host, body, img):
        url = f'http://{host}/demo/'
        r = requests.post(url, data=json.dumps(body))
        print(img, r.json().get('results'))
        ret = r.json().get('results')[0]['status']
        if ret != 'OK':
            print(img, ret)
    
    
    
    
    req('10.10.23.17:3345', './mypic/')
    
    32 条回复    2021-03-31 18:43:37 +08:00
    BrettD
        1
    BrettD  
       301 天前 via iPhone
    ulimit 不行吗
    liprais
        2
    liprais  
       301 天前 via iPhone   ❤️ 2
    yield 那里缩进不对
    CRVV
        3
    CRVV  
       301 天前
    1. 发 HTTP 请求不需要用多进程
    2. 如果在乎性能,请用 requests.session
    3. 如果单线程顺序发请求不够快,可以用 ThreadPoolExecutor 或者 aiohttp
    sss495088732
        4
    sss495088732  
       301 天前
    做个缓冲区....要限制一下并行的进程,句柄数...你这能跑到一半儿,那电脑也是非常不戳
    UN2758
        5
    UN2758  
       301 天前
    你定义的 img_to_base64 是单纯的生成器,想要协程支持的生成器,得给它加上 @asyncio.coroutine
    UN2758
        6
    UN2758  
       301 天前
    @UN2758 #5 看错
    xiaolinjia
        7
    xiaolinjia  
       301 天前
    开了 1w 多进程牛的,整个 multiprocessing.Pool 进程池限制下并行数吧。
    css3
        8
    css3  
    OP
       301 天前
    @BrettD mac 8192 跑满了

    @liprais 这个缩进应该没问题吧,每处理 1 张,yield 出来

    @CRVV 为啥不需要用多进程呢

    @sss495088732 不知道咋搞缓冲区


    @UN2758 老哥想表达啥,我没有看太懂啊

    @xiaolinjia 好的,多谢,我找下文档
    ch2
        9
    ch2  
       301 天前
    1 万个任务正常做法是用进程池开 n 个进程(n<=你的 cpu 核心数*2)
    分批陆续完成,而不是 1 万个进程一拥而上
    而且你的这个代码瓶颈在网速上,根本不需要多进程,用多线程就能处理
    但是最优的做法是用 grequests 一次批量发 1000 个请求,配合上错误重试,分 10-20 次就搞定了
    ch2
        10
    ch2  
       301 天前
    随便找到网图,grequests 是编程最简单的大批量发 http 请求的方法,几乎没有唯二的其他选择,你会用 requests 就会用 grequests
    hsfzxjy
        11
    hsfzxjy  
       301 天前 via Android
    @css3 你要等文件关了再 yield
    shuax
        12
    shuax  
       301 天前
    开 100 个进程差不多了,再多也不快。
    zonyitoo
        13
    zonyitoo  
       301 天前
    每个图标单独开一个进程去跑,这居然能跑得动电脑的配置也着实厉害了
    renmu123
        14
    renmu123  
       301 天前 via Android
    hhttp 直接开个进程池就可以了,或者用 grequest 。基本都是开箱即用
    css3
        15
    css3  
    OP
       301 天前 via iPhone
    @ch2 感谢老哥,学习了,我试试这个库。
    @hsfzxjy,我的理解是 with 自动会关闭呢,另外我也尝试把 yield 给放到 with 外边,也还是这个问题,应该主要问题出在多进程哪里,如同 13 扣老哥说的
    @shuax 嗯,多谢老哥
    @zonyitoo 学业不精,惭愧
    @renmu123 多谢,我试试
    aladdindingding
        16
    aladdindingding  
       301 天前
    ulimit 改成 65535
    css3
        17
    css3  
    OP
       301 天前
    @aladdindingding 本质是我进程和图片数一致了,这里有问题,改这个值,解决不了本质上的问题呢
    LeeReamond
        18
    LeeReamond  
       301 天前 via Android
    你这个需求 py 的最佳实践应该是多线程 ffi 加异步 io,大概会比你现在的方案快很多很多很多很多
    laqow
        19
    laqow  
       301 天前 via Android
    看起来是 2 楼的问题,yield 写 with 里面了,每次迭代文件都没关

    不改程序的话用 linux,想开多少文件开多少
    laqow
        20
    laqow  
       301 天前 via Android
    感觉这样写每个图都是主进程开的,还没进 worker 就已经错误了
    css3
        21
    css3  
    OP
       301 天前
    @laqow 我把 yield 放到 with 外,也是跑一半 OSError: [Errno 24] Too many open files, 放 linux 服务器上跑,跑的时间久点儿,最终也会 OSError: [Errno 24] Too many open files
    LeeReamond
        22
    LeeReamond  
       301 天前 via Android   ❤️ 1
    @css3 跟 yield 没有关系,yield 只是起到保存状态中断执行的作用,你在循环里每次迭代,生成器也循环,with 管理器是正常结束的。另外仔细看了一下你的代码,你的多进程似乎仅负责网络通信,这是非常不合理的使用方法,建议了解 python 中的异步网络通信
    LeeReamond
        23
    LeeReamond  
       301 天前 via Android
    @LeeReamond 你每新建进程,系统要开辟专门的文件指标指向输入输出流,而进程内部又为网络访问开辟了专门的文件。且 tcp 访问后有 timewait 状态,占用文件不会立即被释放,导致你的资源吃满。现代服务器单机每秒可以处理几十万个请求,即使用 python 也一样,绝不是你这仅仅一万个不现实请求能搞崩的。一个简单的多访问问题被你搞成这样。
    mjawp
        24
    mjawp  
       301 天前
    这种业务需求应该是多线程比较好吧?

    1.多进程切换开销比多线程切换开销大
    2.合理的进程数应该是等于你的 cpu 核心数
    3.速度瓶颈主要还是等待请求和 IO,所以在等待 IO 与网络请求的时候可以切换很多很多不同的线程进行其他操作了
    imn1
        25
    imn1  
       301 天前
    @liprais #2 说的是关键点
    不要在 with open 里面 yield
    yield 相当于生成器,数据是集中处理的,不是逐个处理,这就造成打开太多
    简单说就是全部 yield 的数据 都 获取后集中才处理,这时每个 open 都没有 close
    imn1
        26
    imn1  
       301 天前
    前置重点:看下面第四点

    我做过类似的,不过不是 base64,而是 CRC32,应该比 base64 耗时,8K 张图片
    建议:
    1. base64 移出 os.walk,同时也建议 os.scandir 替换 walk,递归只 yield 返回路径就好了
    2. 多进程可以尝试换成 Pool+Pool.imap(),注意要用 close(),参考手册,Pool.close 要在 Pool.join 前面,同时限制线程数量
    3. 小问题,扩展名列表只有小写,你确保一万多文件扩展名都没有大写字母么?不小心会漏掉文件的
    4. 最后是严重的逻辑错误,img_to_base64 里面的 r 是个字典,你最后 return 一次就够了,怎么是不停 yield 这个字典呢?我觉得这是最大问题

    我以前考虑是遍历的同时处理文件,还是遍历了路径再处理文件,后来我看到遍历树是递归+yield,就不纠结了
    递归里面处理文件,处理文件 yield 结果,这两个都不是好想法,肯定有说不清的问题(因为 python 是调用系统 API 打开文件的),所以直接就用递归 yield 路径,然后再考虑其他方式优化文件处理
    imn1
        27
    imn1  
       301 天前
    补充:你这样重复 yield 这个字典,里面还有 open,这样不是打开一万次(每文件一次),而是打开一万次的阶乘!!
    julyclyde
        28
    julyclyde  
       301 天前
    字典还能 yield ??
    slidoooor
        29
    slidoooor  
       301 天前
    学习了,感谢!
    itwhat
        30
    itwhat  
       300 天前
    IO 密集型应考虑多线程
    css3
        31
    css3  
    OP
       300 天前
    @itwhat 请教下,图片转 base64 仅仅是 IO 密集型吗?
    maybedk
        32
    maybedk  
       300 天前
    r 那个字典没必要直接 yield (img,bs64),与 with 对齐;
    开进程池 pool = multiprocessing.Pool(4);
    基本能跑起来,速度多快不知道
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1248 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 23:19 · PVG 07:19 · LAX 15:19 · JFK 18:19
    ♥ Do have faith in what you're doing.