python并发与并行(一) ———— 用subprocess管理子进程

并发(concurrency)指计算机似乎能在同一时刻做许多件不同的事情。例如,在只配有一个CPU核心的计算机上面,操作系统可以迅速切换这个处理器所运行的程序,因此尽管同一时刻最多只有一个程序在运行,但这些程序能够交替地使用这个核心,从而造成一种假象,让人觉得它们好像真的在同时运行。

并行(parallelism)与并发的区别在于,它强调计算机确实能够在同一时刻做许多件不同的事情。例如,若计算机配有多个CPU核心,那么它就真的可以同时执行多个程序。每个CPU核心执行的都是自己的那个程序之中的指令,这些程序能够同时向前推进。

并行与并发之间的区别,关键在于能不能提速(speedup)。如果程序把总任务量分给两条独立的执行路径去同时处理,而且这样做确实能让总时间下降到原来的一半,那么这就是并行,此时的总速度是原来的两倍。反过来说,假如无法实现加速,那即便程序里有一千条独立的执行路径,也只能叫作并发,因为这些路径虽然看起来是在同时推进,但实际上却没有产生相应的提速效果。

用subprocess管理子进程

我们可以通过subprocess来执行命令行工具,因为shell脚本越写会越复杂,所以改用python来实现,会更容易理解与维护。

python 复制代码
import subprocess

result=subprocess.run(
    ['echo', 'hello from the child process!'],
    capture_output=True, #告诉`subprocess.run()`函数捕获命令的标准输出和标准错误

    encoding='utf-8'
)

result.check_returncode()
print(result.stdout)
python 复制代码
import subprocess

# 这里使用 subprocess.Popen 来启动一个新的子进程。在这个例子中,子进程执行的命令是 sleep 1,该命令会使进程休眠1秒钟。Popen 是 subprocess 模块中用于启动子进程的一个类,与 subprocess.run 不同的是,Popen 提供了更多的控制和灵活性。
proc=subprocess.Popen(['sleep','1'])

# proc.poll()方法用于检查子进程的退出状态。如果子进程仍在运行,则poll()方法返回None。在这个循环中,只要子进程还在运行(即 proc.poll()返回None`),就会不断打印 'Working...'。这实际上是在模拟一种"等待"机制,虽然在实际应用中,更推荐使用事件或其他同步机制来等待进程完成。
while proc.poll() is None:
    print('Working...')

print('Exit status',proc.poll())

Output:

python 复制代码
...
working...
Working...
Working...
Working...
Working...
Working...
Working...
Working...
Exit status 0

subprocess.Popen相比subprocess.run的优点是,使用Popen启动的子进程,可以在子进程启动之后,python的主进程可以去做其他的事情,每隔一段时间来查询一次子进程的状态即可。

subprocess.run 和 subprocess.Popen 是 Python 的 subprocess 模块中用于执行外部命令的两个主要函数/类,但它们在使用和功能上有一些显著的区别。

subprocess.run

便捷性:subprocess.run 是一个高级函数,为运行命令并等待其完成提供了一个简单的接口。它非常适合用于"一次性"命令执行,其中你不需要与长时间运行的进程进行交互。

返回值:subprocess.run 返回一个 CompletedProcess 对象,其中包含有关运行进程的信息,如返回码、标准输出和标准错误输出。

等待进程:subprocess.run 会等待进程完成,然后返回。你不需要手动管理子进程的生命周期。

用法示例:

python 复制代码
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)  
print(result.stdout)

subprocess.Popen

灵活性:subprocess.Popen 是一个类,它提供了更多的控制和灵活性。它允许你启动一个子进程并与其输入/输出/错误管道、返回码等进行交互。

异步执行:与 subprocess.run 不同,subprocess.Popen 不会等待子进程完成。你可以启动一个进程并继续在 Python 脚本中执行其他操作,同时子进程在后台运行。

手动管理:你需要手动管理 Popen 实例的生命周期,包括等待其完成和检查返回码等。

用法示例:

python 复制代码
proc = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)  
stdout, stderr = proc.communicate()  
print(stdout.decode())

总结

如果你只需要简单地运行一个命令并等待其完成,同时获取其输出,那么 subprocess.run 是更好的选择。

如果你需要更复杂的进程控制(例如,与子进程进行交互、在子进程运行时执行其他操作、或处理多个并行的子进程),那么 subprocess.Popen 将是更合适的选择。

把子进程从父进程中剥离,可以让程序平行地运行多条子进程。例如,我们可以像下面这样,先把需要运行的这些子进程用Popen启动起来。

python 复制代码
import subprocess
import time
start=time.time()
sleep_procs=[]
for _ in range(10):
    proc=subprocess.Popen(['sleep','1'])
    sleep_procs.append(proc)

for proc in sleep_procs:
    # communicate() 方法会等待进程完成,并处理其标准输出和标准错误(如果有的话)。在这个例子中,sleep 命令没有输出,所以 communicate() 主要用于等待每个子进程完成。
    proc.communicate()

end=time.time()
delta=end-start
print(f'Finished in {delta:.3} seconds')

Output:

python 复制代码
Finished in 1.01 seconds

从统计结果可以看出,这10条子进程确实表现出了平行的效果。假如它们是按顺序执行的,那么程序耗费的总时长至少应该是10秒,而不是现在看到的1秒左右.

另外,值得一提的是,在这个特定的例子中,使用 proc.communicate() 可能不是等待进程完成的最高效方法,因为它主要是用于处理进程的输出。由于 sleep 命令没有输出,所以这里使用 proc.wait() 可能更为合适,它仅仅等待进程完成而不涉及任何输出处理。不过,在这个例子中,两者之间的性能差异应该是微不足道的。

相关推荐
Juchecar24 分钟前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户83562907805134 分钟前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_35 分钟前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机7 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机8 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机8 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机8 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i9 小时前
drf初步梳理
python·django
每日AI新事件9 小时前
python的异步函数
python
用户31187945592189 小时前
Kylin Linux 10 安装 glib2-devel-2.62.5-7.ky10.x86_64.rpm 方法(附安装包)
linux