美文网首页
python学习笔记之--多进程编程

python学习笔记之--多进程编程

作者: itsenlin | 来源:发表于2022-04-13 23:26 被阅读0次

简介

前面学习了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执行程序。如果shellTrue,更推荐将 args作为字符串传递而非序列。
     subprocess.Popen("ls -l", shell=True)
    
  • stdinstdoutstderr表示要执行的命令的标准输入、输出、错误的文件句柄。可以是subprocess.PIPEsubprocess.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)
  • argsstdinstdoutstderr与Popen类对应参数一样
  • input参数将被传递给 Popen.communicate()以及子进程的 stdin。 如果使用此参数,它必须是一个字节序列。当使用此参数时,在创建内部 Popen对象时将自动带上 stdin=PIPE,并且不能再手动指定 stdin 参数。
  • 如果 capture_output 设为 true,stdout 和 stderr 将会被捕获。在使用时,内置的 Popen对象将自动用 stdout=PIPEstderr=PIPE 创建。stdoutstderr 参数不应当与 capture_output 同时提供。如果你希望捕获并将两个流合并在一起,使用 stdout=PIPEstderr=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主要是调用外部的命令,或者脚本,并与外部程序交互

相关文章

网友评论

      本文标题:python学习笔记之--多进程编程

      本文链接:https://www.haomeiwen.com/subject/lakzsrtx.html