项目地址:https://github.com/qmj0923/Concurrent-Executor-for-Python
需求
- 以不同参数调用同一函数(类似Python内置的map()函数),通过并发执行来缩短运行时间。
- 中途自动保存子任务的执行结果。整个程序如果发生中断,重新执行时可以从中断处继续。
- 显示进度条,能够观察到并发执行的进度。
现有工具
- concurrent.futures.Executor.map:满足需求1,但不满足需求2和需求3。
- tqdm.contrib.concurrent.process_map:满足需求1。不满足需求2。需求3显示了总进度条,但无法观察每个子任务的执行进度。
项目框架
所实现框架可以应用于任何可调用的函数。这里随便写了个foo()
函数,假定我们需要执行foo(0), foo(1), ..., foo(4999)
。那么可以像下面这样套用框架,从而开始并发地执行。
import time
from concurrent_executor import ConcurrentExecutor
def foo(x):
if x % 300 == 7:
raise ValueError('foo')
time.sleep(0.01)
return x
if __name__ == '__main__':
executor = ConcurrentExecutor()
result = executor.run(
data=[[i] for i in range(5000)],
func=foo,
output_dir='data/',
)
这里详细解释一下传递给ConcurrentExecutor.run()
的三个参数。
func
参数是需要被多次调用的函数。data
参数是一个列表,其中每个元素都会成为func
函数的输入(即func
实参构成的列表)。也就是说,这个框架其实是在执行func(*data[0])
,foo(*data[1])
, …。output_dir
参数是一个目录,用于存储中间文件,以便中断后的恢复。
更多的参数详见ConcurrentExecutor.run()函数中的注释,这里不再赘述。
运行效果
类似tqdm.contrib.concurrent.process_map,可以在命令行中显示总进度:
2023-06-18 14:47:20,987 INFO 0%| | 0/5 [00:00<?, ?it/s]
2023-06-18 14:47:37,224 INFO 20%|## | 1/5 [00:16<01:04, 16.24s/it]
2023-06-18 14:47:37,340 INFO 40%|#### | 2/5 [00:16<00:20, 6.75s/it]
2023-06-18 14:47:37,342 INFO 100%|##########| 5/5 [00:16<00:00, 3.27s/it]
在输出目录下会创建一个_tmp/
文件夹。其中的log_<i>.log
实时记录子任务的进度,part_<i>.json
用于保存子任务的执行结果:
<output_dir>
└── _tmp
├── log_<i>.log
└── part_<i>.json