一、进程和线程

进程和线程是为了解决多任务问题而产生的概念,什么是多任务了?计算机的多任务是指计算设备同时执行不同的事情,过去CPU是单核的,如果要执行多任务,操作系统会让CPU先执行任务1 0.01秒 再执行任务2 0.01秒,接着执行任务3 0.01秒…回到执行任务1,对于单核CPU,多任务其实就是单任务,只不过切换和执行的时间很快,看起来是多任务执行。
随着芯片技术的不断发展,现在的计算设备芯片普遍是多核CPU,所以“多任务”就是在同一时间执行不同的任务。

而一个任务就需要一个进程来处理,操作系统会给每个任务分配一个进程,比如说你要用电脑看视频,同时又要写文档记笔记,还要用浏览器查资料,这三个任务:看视频、记笔记、查资料,操作系统会分配三个进程来对应。Windows系统的任务管理器可以很直观的表现出来:

subprocess 带shell参数_python

有些进程不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部要同时做多个事情,就需要同时运行多个“子任务”,这些子任务称为-——线程。线程是最小的执行单元,每个进程由至少一个线程组成。

多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。如果一个计算设备是多核多线程的CPU,比如4核8线程,该设备上执行多任务就是真正地同时执行。

总结:多任务的实现方式有三种:

  • 多进程
  • 多线程
  • 多进程+多线程
    python既支持多进程,也支持多线程。其中python自带的subprocess就是支持多进程编程。

二、python subprocess模块

subprocess模块用来代替os.system和os.spawn*,使用该模块可以很方便地大量创建新的进程,连接输入/输出/错误管道,以及获取进程的返回值。

python3.5 以上的版本建议使用subprocess.run()方法,python3.5以下的版本可以使用subprocess.call()

下面我们看下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)

subprocess.run()方法的使用介绍网上已经有很多,这里就不一一介绍,主要记录下我在使用这个方法的时候遇到的一些疑难点吧
args:需要执行的指令,可以是序列,也可以是字符串
capture_output=True:表示stdout 和 stderr都会捕获。但是这样设置不能保证同时能获取stdout和stderr,如果想要同时获取,可以使用stdout=subprocess.PIPE, stderr=subprocess.PIPE来代替capture_out。
timeout:timeout参数被传递给popen . communication()。如果超时过期,子进程将被杀死并等待。子进程终止后将重新引发TimeoutExpired异常。
input:该参数会传递给popen . communication(),从而传递给子进程的标准输入。如果使用,则必须为字节序列,如果指定了encoding或errors或text为true则必须为字符串。当该参数被使用时,内部Popen对象会自动使用stdin=PIPE创建,而stdin参数可能也不会被使用。
check:如果check为true,并且进程以非零的退出码退出,则会引发一个CalledProcessError异常。该异常的属性包含参数、退出代码以及stdout和stderr(如果它们被捕获的话)。
encoding/errors如果encoding或errors被指定,或者text=True, stdin、stdout和stderr的文件对象将使用指定的encoding和errors以文本模式打开或者默认为io.TextIOWrapper。universal_newlines参数等价于text,提供它是为了向后兼容。默认情况下,文件对象以二进制模式打开。

subprocess.run(["ls", "-l"])  # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)

subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
  ...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')

注意:python3.5开始才支持subprocess.run()方法,3.6 添加了 encoding 和errors参数,3.7添加了text、capture_output参数,universar_newlines是为了更好的兼容下面的版本,它和text是等价的

subprocess模块的方法其实都是有Popen构造函数实现的,Popen可以灵活地创建和管理进程,以便开发人员能够处理subprocess函数没有涉及的不太常见的情况。

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()方法:
popen.communicate(input=None, timeout=None): 进程间通信,比如adb shell进入嵌入式linux设备后,我需要在adb shell 里执行设备厂商自行设计的脚步程序,我们可以这样设计自己的代码。

def cmd_with_record(cmd,timeout=None):

    log = open(logfileName,'a')
    # proc = Popen(["cmd"], shell=False, stdout=PIPE, stdin=PIPE, stderr=PIPE)
    proc = Popen(["cmd"], shell=False, stdout=log, stdin=PIPE, stderr=PIPE)
    # log.close()
    commands = cmd
    outs, errs = proc.communicate(commands.encode("gbk"),timeout=timeout)
    log.close()

popen.communicate()返回一个元组(stdout_data, stderr_data), 如果text=True,则返回的是字符串类型,否则是字节数据(byte)

参考:

  1. 官方教程-Subprocess
  2. 廖雪峰Python-多进程
  3. 油管subprocess教程