python 多进程

什么是进程?

进程是操作系统中最基本的概念。在多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各程序的活动规律引进的一个概念。

  • 狭义定义:程序的一个执行实例;
  • 广义定义:进程是一个具有一定独立功能的程序,是关于某个数据集合的一次运行活动,是系统进行资源分配和调度的基本单位
    进程是线程的容器。

进程的概念主要有两点:

  • 进程是一个实体,每个进程有自己的地址空间:

    • 文本区域(text region):程序的源指令
    • 数据区域(data region):静态变量
    • 堆(heap region): 动态内容分配区域
    • 栈(stack region):保存局部变量等
  • 进程是一个“执行中的程序”:程序是指令、数据及组织形式的描述,进程是程序的实体。

进程的基本状态

  • 就绪状态:分配了除CPU以外所有的资源,只要获得CPU即可执行
  • 执行状态
  • 阻塞状态:正在执行的时候由于一些事件无法继续执行,放弃CPU,处于暂停状态
  • 挂起状态:如发现程序有问题,希望暂时停下来,即暂停运行

进程的通信方式

  • 管道(Pipe):管道可以用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间通信。
  • 命名管道(Namedpipe):出了拥有管道的功能外,它还可用于无亲缘关系进程间的通信,其在文件系统中有对应的文件件名。
  • 信号(Signal):用于通知接收进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身。
  • 消息队列:消息队列是消息的链接表,包括Posix消息队列与system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
  • 共享内存:多个进程可以访问同一块内存空间,是最快的可用IPC形式。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥
  • 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间。
  • 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  • 套接字(socket):一般用于不同机器之间的进程间通信。

多进程

进程可以创建子进程,子进程是完全独立运行的实体,每个子进程都拥有自己的私有系统状态和执行主线程。
因为子进程是独立的,所以它可以与父进程并发执行。也就是说,父进程可以处理事件1,同时,子进程可以在后台处理事件2。

为什么要使用多进程?

Python支持线程,但是Python的线程受到很多限制,因为Python解释器使用了内部的全局解释锁(GIL),Python的执行由Python虚拟机控制,Python解释器可以运行多个线程,但是任意时刻只允许单个线程在解释器中执行,对Python虚拟机的访问由全局解释锁(GIL)控制

GIL保证同一个时刻仅有一个线程在解释器中执行。无论系统上有多少个CPU,Python只能在一个CPU上运行。
因为Python中使用全局解释器锁(GIL),他将进程中的线程序列化,导致多核CPU实际上并不能达到并行提高速度的目的。
而进程不受此限制。

什么时候使用多进程?

不使用情况:如果每个进程执行需要消耗的时间非常短,则不必使用多进程,因此进程的启动与关闭也会耗费资源。如果是IO密集型(文件读取,爬虫等)则可以使用多线程去处理。

使用的情况:多进程往往是用来处理CPU密集型(科学计算)的需求。

为什么使用multiprocessing模块

Unix/Linux操作系统提供fork()系统调用,它非常特殊,普通的函数调用一次,返回一次,它调用一次,返回两次。
因此操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

子进程永远返回0,父进程返回子进程的pid
一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

使用os模块创建两个进程的例子:

import os
print "Process (%s) start..." % os.getpid()

pid = os.fork()
if pid == 0:
    print "This is child process (%d)" % os.getpid()
else:
    print "This is parent process (%d), his child process is %d " % (os.getpid(), pid)

运行结果如下:

Process (61) start...
This is parent process (61), his child process is 62
This is child process (62)

由于Windows没有fork调用,上面的代码在Windows上无法运行。
由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。

multiprocessing常用组件与功能

创建与管理模块

  • Process(用于创建进程模块)
  • Pool(用于创建管理进程池)
  • Queue(用于进程通信,资源共享)
  • Value,Array(用于进程通信,资源共享)
  • Pipe(用于管道通信)
  • Manager(用于资源共享)

同步子进程模块

  • Condition
  • Event
  • Lock
  • RLock
  • Semaphore

multiprocessing中Process类

原型:

class Process(__builtin__.object)
...
 |  **__init__(self, group=None, target=None, name=None, args=(), kwargs={})**
...

multiprocessing提供Process类来代表进程,一个Process对象代表一个进程对象。
Process的实例p具有的属性。

- p.is_alive()
如果p在运行,返回True

- p.join([timeout])
等待进程p运行结束
timeout是可选的超时期限。如果timeout为None,则认为要无限期等待

- p.run()
进程启动时运行的方法。默认情况下,会调用传递给Process构造函数中的target
定义进程的另一种方法是继承Process并重写run()方法

- p.start()
运行进程p,并调用p.run()

- p.terminate()
强制杀死进程。如果调用此方法,进程p将被立即终止,同时不会进行任何清理动作。如果进程p创建了自己的子进程,这些进程将会变成僵尸进程
此方法要小心使用
> If this method is used when the associated process is using a pipe or queue then the pipe or queue is liable to become corrupted and may become unusable by other process. Similarly, if the process has acquired a lock or semaphore etc. then terminating it is liable to cause other processes to deadlock.

- p.authkey
进程的身份验证键

- p.daemon
守护进程标志,布尔变量。指进程是否为后台进程。如果该进程为后台进程(daemon = True),当创建它的Python进程终止时,后台进程将自动终止
>p.daemon的值要在使用p.start()启动进程之前设置,禁止后台进程创建子进程

- p.exitcode
进程的整数退出码。如果进程仍在运行,值为None。如果值为-N,表示进程由信号N终止

- p.name
进程名

- p.pid
进程号
>Note that the start(), join(), is_alive(), terminate() and exitcode methods should only be called by the process that created the process object.

下面,演示启动一个子进程并等待其结束:

from multiprocessing import Process
import os

def run_proc(name):
    print "Run child process %s (%s)..." % (name, os.getpid())

if __name__ == "__main__":
    print "Parent Process %s." % os.getpid()
    p = Process(target=run_proc, args=('test',))
    print "Process will start."
    p.start()
    p.join()
    print "Process end."

运行结果

Parent Process 104.
Process will start.
Run child process test (105)...
Process end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

参考文献

Was this helpful?

0 / 0

发表回复 0