python学习笔记之--多进程编程
2022-04-13 本文已影响0人
itsenlin
简介
前面学习了python语言的多线程编程,由于全局解释器锁的引入,多线程编程无法充分利用多CPU的性能。而python提供了一个与多线程编程类似的API的包实现多进程编程模块--multiprocessing
,多进程没有全局解释器锁的限制,也即可以利用多CPU的性能。
multiprocessing模块
根据不同的平台, multiprocessing
支持三种启动进程的方法。
进程启动方式 | 适用平台 | 说明 |
---|---|---|
spawn |
windowns/unix | 父进程会启动一个全新的 python 解释器进程。 子进程将只继承那些运行进程对象的 run() 方法所必需的资源。 |
fork |
unix | 使用os.fork() 生成Python解析器fork分支,此方式在多线程场景下不安全 |
forkserver |
unix | 启动一个服务器进程,每当需要一个新进程时,父进程就会连接到服务器并请求它fork一个新进程。服务器进程是单线程的,因此使用 os.fork() 是安全的。 |
可以通过模块的接口set_start_method()
来改变进程启动的方式,注意,此接口只能调用一次。
此模块还提供了所有与threading
类似的同步原语,以及进程间通讯方式,详细信息参见官方文档
subprocess模块
python语言还提供了一个subprocess
模块,此模块主要是生成新的进程,连接它们的输入、输出、错误管道,并且获取它们的返回码。打算代替一些老旧的模块与功能:
os.system()
os.spwan*()
此模块提供了两种生成子进程的接口:run()
和Popen
; 后者比较底层一些,灵活性更强。前者底层也是用后者来实现的。
Popen类
原型如下:
class subprocess.Popen(args, bufsize=- 1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=- 1, encoding=None, errors=None, text=None, pipesize=- 1)
有很多个默认参数,这也是为什么说Popen接口比较灵活的原因了。在unix平台下使用类似于os.execvpe
接口实现;而在windowns下使用CreateProcess()
来实现。
-
args
是一个程序参数的序列或者是一个单独的字符串。 推荐使用序列形式。 如果args
是字符串,则其解读依赖于具体平台。 例如:subprocess.Popen(["ls", "-l"])
-
shell
指定是否使用shell
执行程序。如果shell
为True
,更推荐将args
作为字符串传递而非序列。subprocess.Popen("ls -l", shell=True)
-
stdin
、stdout
、stderr
表示要执行的命令的标准输入、输出、错误的文件句柄。可以是subprocess.PIPE
、subprocess.DEVNULL
、已存在的文件描述符或者None
。最常用的是PIPE
表示创建一个连接子进程的管道。>>> p = subprocess.Popen("ls -l", shell=True, stdout=subprocess.PIPE) >>> p.stdout.read()
常用的类方法
Popen类提供了很多很有用的方法,常用的有以下几个
方法 | 说明 |
---|---|
wait(timeout=None) |
等待子进程被终止。设置并返回 returncode 属性。使用管道时有可能产生死锁,可以使用communicate 接口来规避。 |
communicate(input=None, timeout=None) |
与进程交互:将数据发送到 stdin。 从 stdout 和 stderr 读取数据,直到抵达文件结尾。 等待进程终止并设置 returncode 属性。 |
poll() |
检查子进程是否已被终止。设置并返回 returncode 属性。否则返回 None 。此接口不阻塞。 |
还有以下几个常用的属性
属性 | 说明 |
---|---|
Popen.stdin |
如果 stdin 参数为 PIPE ,此属性是一个类似 open() 返回的可写的流对象。 |
Popen.stdout |
如果 stdout 参数为 PIPE ,此属性是一个类似 open() 返回的可读的流对象。从流中读取子进程提供的输出。 |
Popen.stderr |
如果 stderr 参数为 PIPE ,此属性是一个类似 open() 返回的可写的流对象。从流中读取子进程提供的错误信息。如果执行正常则返回空字符串。 |
Popen.returncode |
此进程的退出码,由 poll() 和 wait() 设置(以及直接由 communicate() 设置)。一个 None 值 表示此进程仍未结束。 |
>>> p = subprocess.Popen("ls -l /dev/null", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> p.stdout.read()
b'crw-rw-rw- 1 root root 1, 3 4\xe6\x9c\x88 13 21:49 /dev/null\n'
>>> p.stderr.read()
b''
>>> p.returncode
0
>>>
subprocess.run函数接口
接口原型如下
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs)
-
args
、stdin
、stdout
、stderr
与Popen类对应参数一样 -
input
参数将被传递给Popen.communicate()
以及子进程的 stdin。 如果使用此参数,它必须是一个字节序列。当使用此参数时,在创建内部Popen
对象时将自动带上stdin=PIPE
,并且不能再手动指定stdin
参数。 - 如果
capture_output
设为 true,stdout 和 stderr 将会被捕获。在使用时,内置的Popen
对象将自动用stdout=PIPE
和stderr=PIPE
创建。stdout 和 stderr 参数不应当与 capture_output 同时提供。如果你希望捕获并将两个流合并在一起,使用stdout=PIPE
和stderr=STDOUT
来代替 capture_output。
此接口返回subprocess.CompletedProcess
类的一个对象,也表示子进程运行结束。此类提供以下属性与方法:
属性/方法 | 说明 |
---|---|
args |
被用作启动进程的参数. 可能是一个列表或字符串。 |
returncode |
子进程的退出状态码. 通常来说, 一个为 0 的退出码表示进程运行正常。 |
stdout |
从子进程捕获到的标准输出. 一个字节序列, 或一个字符串。 |
stderr |
捕获到的子进程的标准错误. 一个字节序列, 或者一个字符串。 |
check_returncode() |
如果 returncode 非零, 抛出 CalledProcessError 异常。 |
>>> p = subprocess.run("ls -l /dev/null", shell=True, capture_output=True)
>>> p.stdout
b'crw-rw-rw- 1 root root 1, 3 4\xe6\x9c\x88 13 21:49 /dev/null\n'
>>> p.stderr
b''
>>> p.returncode
0
>>>
总结
python的multiprocessing模块和subprocess模块都可以创建子进程,两者还是有很大的区别的。
- multiprocessing类似threading模块,与主进程使用同样的代码,或者使用主进程中的一个函数,或者在主进程中继承Process类
- subprocess主要是调用外部的命令,或者脚本,并与外部程序交互