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

2021-03-30 10:47:24 +08:00
 css3

业务:有大量(本次测试时 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/')
3541 次点击
所在节点    程序员
32 条回复
css3
2021-03-30 15:01:09 +08:00
@laqow 我把 yield 放到 with 外,也是跑一半 OSError: [Errno 24] Too many open files, 放 linux 服务器上跑,跑的时间久点儿,最终也会 OSError: [Errno 24] Too many open files
LeeReamond
2021-03-30 15:26:10 +08:00
@css3 跟 yield 没有关系,yield 只是起到保存状态中断执行的作用,你在循环里每次迭代,生成器也循环,with 管理器是正常结束的。另外仔细看了一下你的代码,你的多进程似乎仅负责网络通信,这是非常不合理的使用方法,建议了解 python 中的异步网络通信
LeeReamond
2021-03-30 15:34:10 +08:00
@LeeReamond 你每新建进程,系统要开辟专门的文件指标指向输入输出流,而进程内部又为网络访问开辟了专门的文件。且 tcp 访问后有 timewait 状态,占用文件不会立即被释放,导致你的资源吃满。现代服务器单机每秒可以处理几十万个请求,即使用 python 也一样,绝不是你这仅仅一万个不现实请求能搞崩的。一个简单的多访问问题被你搞成这样。
mjawp
2021-03-30 15:41:26 +08:00
这种业务需求应该是多线程比较好吧?

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

我做过类似的,不过不是 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
2021-03-30 17:19:30 +08:00
补充:你这样重复 yield 这个字典,里面还有 open,这样不是打开一万次(每文件一次),而是打开一万次的阶乘!!
julyclyde
2021-03-30 19:29:07 +08:00
字典还能 yield ??
slidoooor
2021-03-30 23:26:47 +08:00
学习了,感谢!
itwhat
2021-03-31 10:00:48 +08:00
IO 密集型应考虑多线程
css3
2021-03-31 10:25:44 +08:00
@itwhat 请教下,图片转 base64 仅仅是 IO 密集型吗?
maybedk
2021-03-31 18:43:37 +08:00
r 那个字典没必要直接 yield (img,bs64),与 with 对齐;
开进程池 pool = multiprocessing.Pool(4);
基本能跑起来,速度多快不知道

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

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

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

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

© 2021 V2EX