使用外部命令

时间:2017-03-16 12:29:32

标签: python subprocess multiprocessing threadpool

我想从Python启动一个外部命令,用于大约8000个文件。每个文件都独立于其他文件进行处理。唯一的限制是在处理完所有文件后继续执行。我有4个物理内核,每个内核有2个逻辑内核(multiprocessing.cpu_count()返回8)。我的想法是使用一组四个并行的独立进程,这些进程将在8个核心中的4个核心上运行。这样我的机器就可以在此期间使用了。

以下是我一直在做的事情:

import multiprocessing
import subprocess
import os
from multiprocessing.pool import ThreadPool


def process_files(input_dir, output_dir, option):
    pool = ThreadPool(multiprocessing.cpu_count()/2)
    for filename in os.listdir(input_dir):  # about 8000 files
        f_in = os.path.join(input_dir, filename)
        f_out = os.path.join(output_dir, filename)
        cmd = ['molconvert', option, f_in, '-o', f_out]
        pool.apply_async(subprocess.Popen, (cmd,))
    pool.close()
    pool.join()


def main():
    process_files('dir1', 'dir2', 'mol:H')
    do_some_stuff('dir2')
    process_files('dir2', 'dir3', 'mol:a')
    do_more_stuff('dir3')

对于一批100个文件,顺序处理需要120秒。上面概述的多处理版本(函数process_files)只需20秒。但是,当我在整个8000个文件集上运行process_files时,我的PC挂起并且在一小时后不会冻结。

我的问题是:

1)我认为ThreadPool应该初始化一个进程池(确切地说是multiprocessing.cpu_count()/2个进程)。但是,我的计算机挂起8000个文件而不是100个,这表明可能没有考虑池的大小。不管怎样,或者我做错了什么。你能解释一下吗?

2)这是在Python下启动独立进程的正确方法,当每个进程都必须启动一个外部命令时,并且这样处理所有资源都不会占用?

2 个答案:

答案 0 :(得分:1)

如果您使用的是Python 3,我会考虑使用map的{​​{1}}方法。

或者,您可以自己管理子流程列表。

以下示例定义了一个函数,用于启动concurrent.futures.ThreadPoolExecutor以将视频文件转换为Theora / Vorbis格式。它为每个已启动的子进程返回一个Popen对象。

ffmpeg

在主程序中,表示正在运行的子进程的def startencoder(iname, oname, offs=None): args = ['ffmpeg'] if offs is not None and offs > 0: args += ['-ss', str(offs)] args += ['-i', iname, '-c:v', 'libtheora', '-q:v', '6', '-c:a', 'libvorbis', '-q:a', '3', '-sn', oname] with open(os.devnull, 'w') as bb: p = subprocess.Popen(args, stdout=bb, stderr=bb) return p 对象列表就像这样保存。

Popen

因此,只有当运行的子进程少于核心时,才会启动新进程。多次使用的代码分为outbase = tempname() ogvlist = [] procs = [] maxprocs = cpu_count() for n, ifile in enumerate(argv): # Wait while the list of processes is full. while len(procs) == maxprocs: manageprocs(procs) # Add a new process ogvname = outbase + '-{:03d}.ogv'.format(n + 1) procs.append(startencoder(ifile, ogvname, offset)) ogvlist.append(ogvname) # All jobs have been submitted, wail for them to finish. while len(procs) > 0: manageprocs(procs) 函数。

manageprocs

def manageprocs(proclist): for pr in proclist: if pr.poll() is not None: proclist.remove(pr) sleep(0.5) 的调用用于防止程序在循环中旋转。

答案 1 :(得分:1)

我认为您的基本问题是使用subprocess.Popen。该方法在返回之前等待命令完成。由于该函数立即返回(即使该命令仍在运行),该函数已完成至于您的ThreadPool,它可以产生另一个...这意味着您最终会产生8000个左右的进程。

使用subprocess.check_call可能会有更好的运气:

Run command with arguments.  Wait for command to complete.  If
the exit code was zero then return, otherwise raise
CalledProcessError.  The CalledProcessError object will have the
return code in the returncode attribute.

所以:

def process_files(input_dir, output_dir, option):
    pool = ThreadPool(multiprocessing.cpu_count()/2)
    for filename in os.listdir(input_dir):  # about 8000 files
        f_in = os.path.join(input_dir, filename)
        f_out = os.path.join(output_dir, filename)
        cmd = ['molconvert', option, f_in, '-o', f_out]
        pool.apply_async(subprocess.check_call, (cmd,))
    pool.close()
    pool.join()

如果您真的不关心退出代码,那么您可能需要subprocess.call,如果流程中出现非零退出代码,则不会引发异常。