业务:有大量(本次测试时 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/')
1
BrettD 2021-03-30 10:50:11 +08:00 via iPhone
ulimit 不行吗
|
2
liprais 2021-03-30 10:53:46 +08:00 via iPhone 2
yield 那里缩进不对
|
3
CRVV 2021-03-30 10:55:17 +08:00
1. 发 HTTP 请求不需要用多进程
2. 如果在乎性能,请用 requests.session 3. 如果单线程顺序发请求不够快,可以用 ThreadPoolExecutor 或者 aiohttp |
4
sss495088732 2021-03-30 10:56:12 +08:00
做个缓冲区....要限制一下并行的进程,句柄数...你这能跑到一半儿,那电脑也是非常不戳
|
7
xiaolinjia 2021-03-30 11:07:01 +08:00
开了 1w 多进程牛的,整个 multiprocessing.Pool 进程池限制下并行数吧。
|
8
css3 OP @BrettD mac 8192 跑满了
@liprais 这个缩进应该没问题吧,每处理 1 张,yield 出来 @CRVV 为啥不需要用多进程呢 @sss495088732 不知道咋搞缓冲区 @UN2758 老哥想表达啥,我没有看太懂啊 @xiaolinjia 好的,多谢,我找下文档 |
9
ch2 2021-03-30 12:14:41 +08:00
1 万个任务正常做法是用进程池开 n 个进程(n<=你的 cpu 核心数*2)
分批陆续完成,而不是 1 万个进程一拥而上 而且你的这个代码瓶颈在网速上,根本不需要多进程,用多线程就能处理 但是最优的做法是用 grequests 一次批量发 1000 个请求,配合上错误重试,分 10-20 次就搞定了 |
10
ch2 2021-03-30 12:18:40 +08:00
随便找到网图,grequests 是编程最简单的大批量发 http 请求的方法,几乎没有唯二的其他选择,你会用 requests 就会用 grequests
|
12
shuax 2021-03-30 12:48:53 +08:00
开 100 个进程差不多了,再多也不快。
|
13
zonyitoo 2021-03-30 13:24:32 +08:00
每个图标单独开一个进程去跑,这居然能跑得动电脑的配置也着实厉害了
|
14
renmu123 2021-03-30 13:26:30 +08:00 via Android
hhttp 直接开个进程池就可以了,或者用 grequest 。基本都是开箱即用
|
15
css3 OP |
16
aladdindingding 2021-03-30 13:45:09 +08:00
ulimit 改成 65535
|
17
css3 OP @aladdindingding 本质是我进程和图片数一致了,这里有问题,改这个值,解决不了本质上的问题呢
|
18
LeeReamond 2021-03-30 14:33:27 +08:00 via Android
你这个需求 py 的最佳实践应该是多线程 ffi 加异步 io,大概会比你现在的方案快很多很多很多很多
|
19
laqow 2021-03-30 14:45:25 +08:00 via Android
看起来是 2 楼的问题,yield 写 with 里面了,每次迭代文件都没关
不改程序的话用 linux,想开多少文件开多少 |
20
laqow 2021-03-30 14:52:41 +08:00 via Android
感觉这样写每个图都是主进程开的,还没进 worker 就已经错误了
|
21
css3 OP @laqow 我把 yield 放到 with 外,也是跑一半 OSError: [Errno 24] Too many open files, 放 linux 服务器上跑,跑的时间久点儿,最终也会 OSError: [Errno 24] Too many open files
|
22
LeeReamond 2021-03-30 15:26:10 +08:00 via Android 1
@css3 跟 yield 没有关系,yield 只是起到保存状态中断执行的作用,你在循环里每次迭代,生成器也循环,with 管理器是正常结束的。另外仔细看了一下你的代码,你的多进程似乎仅负责网络通信,这是非常不合理的使用方法,建议了解 python 中的异步网络通信
|
23
LeeReamond 2021-03-30 15:34:10 +08:00 via Android
@LeeReamond 你每新建进程,系统要开辟专门的文件指标指向输入输出流,而进程内部又为网络访问开辟了专门的文件。且 tcp 访问后有 timewait 状态,占用文件不会立即被释放,导致你的资源吃满。现代服务器单机每秒可以处理几十万个请求,即使用 python 也一样,绝不是你这仅仅一万个不现实请求能搞崩的。一个简单的多访问问题被你搞成这样。
|
24
mjawp 2021-03-30 15:41:26 +08:00
这种业务需求应该是多线程比较好吧?
1.多进程切换开销比多线程切换开销大 2.合理的进程数应该是等于你的 cpu 核心数 3.速度瓶颈主要还是等待请求和 IO,所以在等待 IO 与网络请求的时候可以切换很多很多不同的线程进行其他操作了 |
25
imn1 2021-03-30 16:29:29 +08:00
@liprais #2 说的是关键点
不要在 with open 里面 yield yield 相当于生成器,数据是集中处理的,不是逐个处理,这就造成打开太多 简单说就是全部 yield 的数据 都 获取后集中才处理,这时每个 open 都没有 close |
26
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 路径,然后再考虑其他方式优化文件处理 |
27
imn1 2021-03-30 17:19:30 +08:00
补充:你这样重复 yield 这个字典,里面还有 open,这样不是打开一万次(每文件一次),而是打开一万次的阶乘!!
|
28
julyclyde 2021-03-30 19:29:07 +08:00
字典还能 yield ??
|
29
slidoooor 2021-03-30 23:26:47 +08:00
学习了,感谢!
|
30
itwhat 2021-03-31 10:00:48 +08:00
IO 密集型应考虑多线程
|
32
maybedk 2021-03-31 18:43:37 +08:00
r 那个字典没必要直接 yield (img,bs64),与 with 对齐;
开进程池 pool = multiprocessing.Pool(4); 基本能跑起来,速度多快不知道 |