本文链接地址 http://quqiuzhu.com/2016/python-multiprocessing/

Python 解释器有一个全局解释器锁(PIL),导致每个 Python 进程中最多同时运行一个线程,因此 Python 多线程程序并不能改善程序性能,不能发挥多核系统的优势,可以通过这篇文章了解。

但是多进程程序不受此影响, Python 2.6 引入了 multiprocessing 来解决这个问题。这里介绍 multiprocessing 模块下的进程,进程同步,进程间通信和进程管理四个方面的内容。 这里主要讲解多进程的典型使用,multiprocessing 的 API 几乎是完复制了 threading 的API, 因此只需花少量的时间就可以熟悉 threading 编程了。

Process

先来看一段代码

from multiprocessing import Process, current_process
def func():
time.sleep(1)
proc = current_process()
proc.name, proc.pid sub_proc = Process(target=func, args=())
sub_proc.start()
sub_proc.join()
proc = current_process()
proc.name, proc.pid
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这是在主进程中创建子进程,然后启动(start) 子进程,等待(join) 子进程执行完,再继续执行主进程的整个的执行流程。

那么,一个进程应该是用来做什么的,它应该保存一些什么状态,它的生命周期是什么样的呢?

一个进程需要处理一些不同任务,或者处理不同的对象。创建进程需要一个 function 和相关参数,参数可以是dict Process(target=func, args=(), kwargs = {})name 可以用来标识进程。

控制子进程进入不同阶段的是 start()join()is_alive()terminate()exitcode 方法。这些方法只能在创建子进程的进程中执行。

进程同步

Lock

锁是为了确保数据一致性,比如读写锁,每个进程给一个变量增加 1 ,但是如果在一个进程读取但还没有写入的时候,另外的进程也同时读取了,并写入该值,则最后写入的值是错误的,这时候就需要锁。

def func(lock):
lock.acquire()
# do mysql query select update ...
lock.release() lock = Lock()
for i in xrange(4):
proc = Process(target=func, args=(lock))
proc.start()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Lock 同时也实现了 ContextManager API, 可以结合 with 语句使用, 关于 ContextManager, 请移步 Python 学习实践笔记 装饰器 与 context 查看。

Semaphore

Semaphore 和 Lock 稍有不同,Semaphore 相当于 N 把锁,获取其中一把就可以执行了。 信号量的总数 N 在构造时传入,s = Semaphore(N)。 和 Lock 一样,如果信号量为0,则进程堵塞,直到信号大于0。

Pipes

Pipe 是在两个进程之间通信的工具,Pipe Constructor 会返回两个端

conn1, conn2 = Pipe(True)
  • 1

如果是全双工的(构造函数参数为True),则双端口都可接收发送,否则前面的端口用于接收,后面的端口用于发送。

def proc1(pipe):
for i in xrange(10000):
pipe.send(i) def proc2(pipe):
while True:
print "proc2 rev:", pipe.recv() pipe = Pipe()
Process(target=proc1, args=(pipe[0],)).start()
Process(target=proc2, args=(pipe[1],)).start()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Pipe 的每个端口同时最多一个进程读写,否则数据会出各种问题

Queues

multiprocessing.Queue 与 Queue.Queue 非常相似。其 API 有qsize(), empty(), full(), put(), put_nowait(), get(), get_nowait(),额外还有close(), join_thread(), cancel_join_thread()。

当 Queue 为 Queue.Full 状态时,再 put() 会堵塞,当状态为 Queue.Empty 时,再 get() 也是。当 put() 或 get() 设置了超时参数,而超时的时候,会抛出异常。

Queue 主要用于多个进程产生和消费,一般使用情况如下

def producer(q):
for i in xrange(10):
q.put(i) def consumer(q):
while True:
print "consumer", q.get() q = Queue(40)
for i in xrange(10):
Process(target=producer, args=(q,)).start()
Process(target=consumer, args=(q,)).start()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

十个生产者进程,一个消费者进程,共用同一个队列进行同步。

有一个简化版本的 multiprocessing.queues.SimpleQueue, 只支持3个方法 empty(), get(), put()。

也有一个强化版本的 JoinableQueue, 新增两个方法 task_done() 和 join()。 task_done() 是给消费者使用的,每完成队列中的一个任务,调用一次该方法。当所有的 tasks 都完成之后,交给调用 join() 的进程执行。

def consumer(q):
while True:
print "consumer", q.get()
q.task_done() jobs = JoinableQueue()
for i in xrange(10):
jobs.put(i) for i in xrange(10):
p = Process(target=consumer, args=(jobs,))
p.daemon = True
p.start() jobs.join()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这个 join 函数等待 JoinableQueue 为空的时候,等待就结束,外面的进程可以继续执行了,但是那10个进程干嘛去了呢,他们还在等待呀,上面是设置了 p.daemon = True, 子进程才随着主进程结束的,如果没有设置,它们还是会一直等待的呢。

Lock、Pipe、Queue 和 Pipe 需要注意的是:尽量避免使用 Process.terminate 来终止程序,否则将会导致很多问题, 详情请移步python 官方文档查看。

进程间数据共享

前一节中, Pipe、Queue 都有一定数据共享的功能,但是他们会堵塞进程, 这里介绍的两种数据共享方式都不会堵塞进程, 而且都是多进程安全的。

共享内存

共享内存有两个结构,一个是 Value, 一个是 Array,这两个结构内部都实现了锁机制,因此是多进程安全的。 用法如下:

def func(n, a):
n.value = 50
for i in range(len(a)):
a[i] += 10 num = Value('d', 0.0)
ints= Array('i', range(10)) p = Process(target=func, args=(num, ints))
p.start()
p.join()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Value 和 Array 都需要设置其中存放值的类型,d 是 double 类型,i 是 int 类型,具体的对应关系在Python 标准库的 sharedctypes 模块中查看。

服务进程 Manager

上面的共享内存支持两种结构 Value 和 Array, 这些值在主进程中管理,很分散。 Python 中还有一统天下,无所不能的 Server process,专门用来做数据共享。 其支持的类型非常多,比如list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value 和 Array 用法如下:

from multiprocessing import Process, Manager

def func(dct, lst):
dct[88] = 88
lst.reverse() manager = Manager()
dct = manager.dict()
lst = manager.list(range(5,10)) p = Process(target=func, args=(dct, lst))
p.start()
p.join()
print dct, '|', lst
Out: {88: 88} | [9, 8, 7, 6, 5]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一个 Manager 对象是一个服务进程,推荐多进程程序中,数据共享就用一个 manager 管理。

进程管理

如果有50个任务要执行, 但是 CPU 只有4核, 你可以创建50个进程来做这个事情。但是大可不必,徒增管理开销。如果你只想创建4个进程,让他们轮流替你完成任务,不用自己去管理具体的进程的创建销毁,那 Pool 是非常有用的。

Pool 是进程池,进程池能够管理一定的进程,当有空闲进程时,则利用空闲进程完成任务,直到所有任务完成为止,用法如下

def func(x):
return x*x pool = Pool(processes=4)
print pool.map(func, range(8))
  • 1
  • 2
  • 3
  • 4
  • 5

Pool 进程池创建4个进程,不管有没有任务,都一直在进程池中等候,等到有数据的时候就开始执行。 
Pool 的 API 列表如下:

  • apply(func[, args[, kwds]])
  • apply_async(func[, args[, kwds[, callback]]])
  • map(func, iterable[, chunksize])
  • map_async(func, iterable[, chunksize[, callback]])
  • imap(func, iterable[, chunksize])
  • imap_unordered(func, iterable[, chunksize])
  • close()
  • terminate()
  • join()

异步执行

apply_async 和 map_async 执行之后立即返回,然后异步返回结果。 使用方法如下

def func(x):
return x*x def callback(x):
print x, 'in callback' pool = Pool(processes=4)
result = pool.map_async(func, range(8), 8, callback)
print result.get(), 'in main'
Out:
[0, 1, 4, 9, 16, 25, 36, 49] in callback
[0, 1, 4, 9, 16, 25, 36, 49] in main
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

有两个值得提到的,一个是 callback,另外一个是 multiprocessing.pool.AsyncResult。 callback 是在结果返回之前,调用的一个函数,这个函数必须只有一个参数,它会首先接收到结果。callback 不能有耗时操作,因为它会阻塞主线程。

AsyncResult 是获取结果的对象,其 API 如下

  • get([timeout])
  • wait([timeout])
  • ready()
  • successful()

如果设置了 timeout 时间,超时会抛出 multiprocessing.TimeoutError 异常。wait 是等待执行完成。 ready 测试是否已经完成,successful 是在确定已经 ready 的情况下,如果执行中没有抛出异常,则成功,如果没有ready 就调用该函数,会得到一个 AssertionError 异常。

Pool 管理

这里不再继续讲 map 的各种变体了,因为从上面的 API 一看便知。

然后我们来看看 Pool 的执行流程,有三个阶段。第一、一个进程池接收很多任务,然后分开执行任务;第二、不再接收任务了;第三、等所有任务完成了,回家,不干了。

这就是上面的方法,close 停止接收新的任务,如果还有任务来,就会抛出异常。 join 是等待所有任务完成。 join 必须要在 close 之后调用,否则会抛出异常。terminate 非正常终止,内存不够用时,垃圾回收器调用的就是这个方法。

Python 学习笔记 多进程 multiprocessing--转载的更多相关文章

  1. python 学习笔记 多进程

    要让python程序实现多进程,我们先了解操作系统的相关知识 Unix/Linux操作系统提供了一个fork()系统调用,他非常特殊,普通的函数调用,调用一次,返回一次,但是fork调用一次, 返回两 ...

  2. python学习笔记-多进程

    multiprocessing from multiprocessing import Process import time def f(name): time.sleep(2) print('he ...

  3. python学习笔记——多进程中的锁Lock

    1 进程锁 python编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一线程访问对象. 在python中我 ...

  4. python学习笔记——多进程中共享内存Value & Array

    1 共享内存 基本特点: (1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝. (2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将 ...

  5. python学习笔记—— 多进程中的 孤儿进程和僵尸进程

    1 基本概述 1.1 孤儿进程和僵尸进程 父进程创建子进程后,较为理想状态是子进程结束,父进程回收子进程并释放子进程占有的资源:而实际上,父子进程是异步过程,两者谁先结束是无顺的,一般可以通过父进程调 ...

  6. python学习笔记——多进程二 进程的退出

    1 进程的退出函数的基础语法 1.1 进程的退出函数 进程的退出含有有os._exit([status])和sys.exit([status])两种,从数据包来看,该退出模块仅在linux或者unix ...

  7. python学习笔记——多进程一 基础概念

    1 进程 进程:程序的一次(从开始到结束)执行过程,属于一个动态过程.是系统进行资源分配和调度的基本单位. 程序:指的是一个文件,磁盘中可执行的代码.属于一个静态文件 注:进程运行时需要把程序加载如内 ...

  8. Python学习笔记5 【转载】基本矩阵运算_20170618

    需要 numpy 库支持 保存链接 http://www.cnblogs.com/chamie/p/4870078.html 1.numpy的导入和使用 from numpy import *;#导入 ...

  9. OpenCV之Python学习笔记

    OpenCV之Python学习笔记 直都在用Python+OpenCV做一些算法的原型.本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段.现在看 到一本国外的新书< ...

随机推荐

  1. 关于js闭包之小问题大错误

    闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量. 如果闭包的作用域中保存着一个 HTML 元素,则该元素无法被销毁.(下面代码来自高程) 刚看到一个关于闭包自己没注 ...

  2. python内置函数的简单使用和介绍

    """内置函数的简单使用和介绍参考链接:https://docs.python.org/3/library/functions.html ""&quo ...

  3. mongoose与express

    一.mongoose的使用1.先创建一个项目目录,初始化:npm init -y2.创建一个server.js文件,在该目录下安装mongoose:cnpm install mongoose3.引入m ...

  4. EDK II之DXE Core框架简介

    本文旨在简单的介绍一下DXE阶段的工作原理: UDK2015的开源代码下载:https://github.com/tianocore/tianocore.github.io/wiki/EDK-II D ...

  5. Docker学习笔记之浅谈虚拟化和容器技术

    0x00 概述 相信所有对 Docker 有所耳闻的朋友都知道,它是一款以容器虚拟化技术为基础的软件,因此在了解有关 Docker 的概念知识和使用方法之前,虚拟化和容器技术是我们不可或缺的基础知识. ...

  6. visual studio 2015下使用gcc调试linux c++开发环境搭建完整详解

    一直以来,相信绝大部分的开发都是windows/mac下做开发,尤其是非嵌入式和qt系的,而开源服务器程序绝大部分都是跑在Linux下,几乎就没有跑在windows下的.一直以来开发人员都是在wind ...

  7. git 随笔

    还有一些git的指令没有用过,记录在这里. 1. 设置远程branch的指令 There is no tracking information for the current branch.Pleas ...

  8. centos 7 安装使用 redis

    1.下载redis,用wget就行,版本在这里找: http://download.redis.io/releases/ 2.安装gcc,tcl,用yum 安装. 3.解压,make ,make in ...

  9. 【Python31--pickle函数】

    一.含义 1.pickle的实质是什么 答:利用一些算法把数据对象转换成“二进制文件”,存储在硬盘上,当然也可以放在数据库或者是另外一台计算机上 2.存放:picking,读取:unpicking 3 ...

  10. python ---24 正则表达式 re模块

    一.正则表达式 1.字符组 ① [abc] 匹配a或b或c ②  [a-z] 匹配a到z之间的所有字⺟ [0-9]匹配所有阿拉伯数字 2.元字符 3.量词 4.重要搭配 ①  .*? ②  .*?x  ...