技术解析

python3 多进程求助 OSError: [Errno 24] Too many open files
0
2021-06-07 11:25:32
idczone

业务:有大量(本次测试时 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 multipro国外服务器cessing 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/')

ulimit 不行吗

yield 那里缩进不对

1. 发 HTTP 请求不需要用多进程
2. 如果在乎性能,请用 requests.session
3. 如果单线程顺序发请求不够快,可以用 ThreadPoolExecutor 或者 aiohttp

做个缓冲区....要限制一下并行的进程,句柄数...你这能跑到一半儿,那电脑也是非常不戳

你定义的 img_to_base64 是单纯的生成器,想要协程支持的生成器,得给它加上 @asyncio.coroutine

看错

开了 1w 多进程牛的,整个 multiprocessing.Pool 进程池限制下并行数吧。

mac 8192 跑满了
这个缩进应该没问题吧,每处理 1 张,yield 出来
为啥不需要用多进程呢
不知道咋搞缓冲区

老哥想表达啥,我没有看太懂啊
好的,多谢,我找下文档

1 万个任务正常做法是用进程池开 n 个进程(n<=你的 cpu 核心数*2)
分批陆续完成,而不是 1 万个进程一拥而上
而且你的这个代码瓶颈在网速上,根本不需要多进程,用多线程就能处理
但是最优的做法是用 grequests 一次批量发 1000 个请求,配合上错误重试,分 10-20 次就搞定了

随便找到网图,grequests 是编程最简单的大批量发 http 请求的方法,几乎没有唯二的其他选择,你会用 requests 就会用 grequests


你要等文件关了再 yield

开 100 个进程差不多了,再多也不快。

每个图标单独开一个进程去跑,这居然能跑得动电脑的配置也着实厉害了

hhttp 直接开个进程池就可以了,或者用 grequest 。基本都是开箱即用

感谢老哥,学习了,我试试这个库。
嗯,多谢老哥
学业不精,惭愧
多谢,我试试

ulimit 改成 65535

本质是我进程和图片数一致了,这里有问题,改这个值,解决不了本质上的问题呢

你这个需求 py 的最佳实践应该是多线程 ffi 加异步 io,大概会比你现在的方案快很多很多很多很多

看起来是 2 楼的问题,yield 写 with 里面了,每次迭代文件都没关
不改程序的话用 linux,想开多少文件开多少

感觉这样写每个图都是主进程开的,还没进 worker 就已经错误了

我把 yield 放到 with 外,也是跑一半 OSError: [Errno 24] Too many open files, 放 linux 服务器上跑,跑的时间久点儿,最终也会 OSError: [Errno 24] Too many open files

跟 yield 没有关系,yield 只是起到保存状态中断执行的作用,你在循环里每次迭代,生成器也循环,with 管理器是正常结束的。另外仔细看了一下你的代码,你的多进程似乎仅负责网络通信,这是非常不合理的使用方法,建议了解 python 中的异步网络通信

你每新建进程,系统要开辟专门的文件指标指向输入输出流,而进程内部又为网络访问开辟了专门的文件。且 tcp 访问后有 timewait 状态,占用文件不会立即被释放,导致你的资源吃满。现代服务器单机每秒可以处理几十万个请求,即使用 python 也一样,绝不是你这仅仅一万个不现实请求能搞崩的。一个简单的多访问问题被你搞成这样。

这种业务需求应该是多线程比较好吧?
1.多进程切换开销比多线程切换开销大
2.合理的进程数应该是等于你的 cpu 核心数
3.速度瓶颈主要还是等待请求和 IO,所以在等待 IO 与网络请求的时候可以切换很多很多不同的线程进行其他操作了

说的是关键点
不要在 with open 里面 yield
yield 相当于生成器,数据是集中处理的,不是逐个处理,这就造成打开太多
简单说就是全部 yield 的数据 都 获取后集中才处理,这时每个 open 都没有 close

前置重点:看下面第四点
我做过类似的,不过不是 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 路径,然后再考虑其他方式优化文件处理

补充:你这样重复 yield 这个字典,里面还有 open,这样不是打开一万次(每文件一次),而是打开一万次的阶乘!!

字典还能 yield ??

学习了,感谢!

IO 密集型应考虑多线程

请教下,图片转 base64 仅仅是 IO 密集型吗?

r 那个字典没必要直接 yield (img,bs64),与 with 对齐;
开进程池 pool = multiprocessing.Pool(4);
基本能跑起来,速度多快不知道

数据地带为您的网站提供全球顶级IDC资源
在线咨询
专属客服