在Python中并行运行多个系统命令

37 投票
7 回答
53243 浏览
提问于 2025-04-16 11:47

我写了一个简单的脚本,用来对一系列文件执行系统命令。为了加快速度,我想把这些命令并行运行,但不是一次性全部运行——我需要控制同时运行的命令数量的上限。有什么简单的方法可以实现这个呢?

7 个回答

8

你需要把一个信号量对象和线程结合起来。信号量是一个可以限制在某段代码中同时运行的线程数量的对象。在这个例子中,我们会用信号量来限制可以执行os.system调用的线程数量。

首先,我们导入需要的模块:

#!/usr/bin/python

import threading
import os

接下来,我们创建一个信号量对象。这里的数字四表示一次可以有四个线程获取这个信号量。这就限制了可以同时运行的子进程数量。

semaphore = threading.Semaphore(4)

这个函数简单地把对子进程的调用包裹在信号量的调用中。

def run_command(cmd):
    semaphore.acquire()
    try:
        os.system(cmd)
    finally:
        semaphore.release()

如果你使用的是Python 2.6及以上版本,这个过程会变得更简单,因为你可以使用'with'语句来同时进行获取和释放的操作。

def run_command(cmd):
    with semaphore:
        os.system(cmd)

最后,为了证明这个方法按预期工作,我们会调用“sleep 10”命令八次。

for i in range(8):
    threading.Thread(target=run_command, args=("sleep 10", )).start()

使用'time'程序运行这个脚本显示,它只需要20秒,因为两组四个的sleep命令是并行运行的。

aw@aw-laptop:~/personal/stackoverflow$ time python 4992400.py 

real    0m20.032s                                                                                                                                                                   
user    0m0.020s                                                                                                                                                                    
sys     0m0.008s 
20

Sven Marnach的回答几乎是对的,但有一个问题。如果最后的max_processes进程中的一个结束了,主程序会尝试启动另一个进程,而这个循环会结束。这会导致主进程关闭,从而可能关闭子进程。对我来说,这种情况发生在使用screen命令的时候。

在Linux中的代码大概是这样的(只适用于python2.7):

import subprocess
import os
import time

files = <list of file names>
command = "/bin/touch"
processes = set()
max_processes = 5

for name in files:
    processes.add(subprocess.Popen([command, name]))
    if len(processes) >= max_processes:
        os.wait()
        processes.difference_update(
            [p for p in processes if p.poll() is not None])
#Check if all the child processes were closed
for p in processes:
    if p.poll() is None:
        p.wait()
34

如果你已经在调用子进程了,我觉得没必要使用线程池。下面是一个使用 subprocess 模块的基本实现:

import subprocess
import os
import time

files = <list of file names>
command = "/bin/touch"
processes = set()
max_processes = 5

for name in files:
    processes.add(subprocess.Popen([command, name]))
    if len(processes) >= max_processes:
        os.wait()
        processes.difference_update([
            p for p in processes if p.poll() is not None])

在Windows系统上,os.wait() 这个方法是不可用的(也没有其他方法可以等待任何子进程结束)。你可以通过定时检查的方式来解决这个问题:

for name in files:
    processes.add(subprocess.Popen([command, name]))
    while len(processes) >= max_processes:
        time.sleep(.1)
        processes.difference_update([
            p for p in processes if p.poll() is not None])

你需要等待的时间取决于子进程的预期执行时间。

撰写回答