31 Python 多进程-multiprocessing

Python 多进程编程 - multiprocessing模块

进程

进程的概念

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

狭义定义:程序的一个执行(运行)实例;

广义定义:进程是一个具有一定独立功能的程序,关于某个数据集合的一次运行活动,是系统进行资源分配和调度(执行)的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

进程的概念主要有两点:

  1. 进程是一个实体。每一个进程都有它自己的地址空间。

    一般情况下,进程包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。

    文本区域,包含程序的源指令;

    数据区域,包含了静态变量;

    堆,动态内存分区区域;

    栈,动态增长与收缩的段,保存本地变量;

  2. 进程是一个“执行中的程序”。

    程序是一个没有生命的实体,只有处理器赋予程序生命时(执行程序),它才能成为一个活动的实体,我们称其为进程。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程的基本状态

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

同步机制遵循的原则:空闲让进 忙则等待 有限等待 让权等待

进程的通信方式

  • 管道(Pipe)

    管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信

  • 命名管道(namedpipe)

    命名管道克服了管道没有名字的限制,因此,除了拥有管道的功能外,它还可用于无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名

  • 信号(Signal)

    信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身

  • 消息队列

    消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。

  • 共享内存

    使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥

  • 内存映射(mappedmemory)

    内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间

  • 信号量(semaphore)

    主要作为进程间以及同一进程不同线程之间的同步手段

  • 套接口(Socket)

    常见的进程间通信机制,可用于不同机器之间的进程间通信

多进程

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

​ 使用多个进程或线程时,操作系统负责安排它们的工作。具体做法是:给每个进程(线程)安排一个小的时间片,并在所有的任务之间快速轮询,给每个任务分配一部分可用的CPU时间。如系统同时运行10个进程,操作系统会给每个进程分配1/10的CPU时间,在10个进程之间快速轮询。在具有多个CPU的系统上,操作系统可以尽可能使用每个CPU,从而并发执行进程。

请记住,在任何一个给定的时刻,程序(进程)都只做一件事情。

Python与并发编程

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

GIL保证同一个时刻仅有一个线程在解释器中执行。无论系统上有多少个CPU,Python只能在一个CPU上运行。

(使用GIL的原因,在多线程访问数据时,保证数据安全)

如果线程涉及大量的CPU操作,使用线程会降低程序的运行速度,见代码。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import time
from threading import Thread
from multiprocessing import Process
from timeit import Timer

def countdown(n):
    while n > 0:
        n -= 1

def t1():
    COUNT=100000000
    thread1 = Thread(target=countdown,args=(COUNT,))
    thread1.start()
    thread1.join()
#    COUNT = 100000000 # 100 million
#    countdown(COUNT)

def t2():
    COUNT=100000000
    thread1 = Thread(target=countdown,args=(COUNT//2,))
    thread2 = Thread(target=countdown,args=(COUNT//2,))
    thread1.start(); thread2.start()
    thread1.join(); thread2.join()

def t3():
    COUNT=100000000
    p1 = Process(target=countdown,args=(COUNT//2,))
    p2 = Process(target=countdown,args=(COUNT//2,))
    p1.start(); p2.start()
    p1.join(); p2.join()

if __name__ == '__main__':
    t = Timer(t1)
    print 'countdown in one thread:',t.timeit(1)
    t = Timer(t2)
    print 'countdown use two thread:',t.timeit(1)
    t = Timer(t3)
    print 'countdown use two Process',t.timeit(1)

    '''
    result:多线程最慢,多进程最快
    countdown in one thread:5.18
    countdown use two thread:18.26
    countdown use two Process:3.22
    '''

常见解决GIL锁的方法:使用多进程,将程序设计为大量独立的线程集合。

multiprocessing模块

multiprocessing模块为在子进程中运行任务、通信、共享数据,以及执行各种形式的同步提供支持。该模块更适合在UNIX下使用。

这个模块的接口与threading模块的接口类似。但是和线程不同,进程没有任何共享状态,因此,如果某个进程修改了数据,改动只限于该进程内,并不影响其他进程。

(不同进程内,id(10)是不一样的,因为每个进程是相互独立的)

Process

Process(group=None, target=None, name=None, args=(), kwargs={})

这个类构造了一个Process进程。表示一个运行在子进程中的任务,应使用关键字参数来指定构造函数中的参数

如果一个类继承了Process,在进行有关进程的操作前,确保调用了Process的构造函数。

group,预留参数,一直为None

target,进程启动时执行的可调用对象,由run()方法调用

name,进程名

args,target处可调用对象的参数,如果可调用对象没有参数,不用赋值

kwargs,target处可调用对象的关键字参数

Process的实例p具有的属性。

  1. p.is_alive()

    如果p在运行,返回True

  2. p.join([timeout])

    等待进程p运行结束

    timeout是可选的超时期限。如果timeout为None,则认为要无限期等待

  3. p.run()

    进程启动时运行的方法。默认情况下,会调用传递给Process构造函数中的target

    定义进程的另一种方法是继承Process并重写run()方法

  4. p.start()

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

  5. 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.

  6. p.authkey

    进程的身份验证键

  7. p.daemon

    守护进程标志,布尔变量。指进程是否为后台进程。如果该进程为后台进程(daemon = True),当创建它的Python进程终止时,后台进程将自动终止

    p.daemon的值要在使用p.start()启动进程之前设置

    禁止后台进程创建子进程

  8. p.exitcode

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

  9. p.name

    进程名

  10. 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.

code

如果在windows平台,需要在cmd下运行,不能在idle运行。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing,time

class Clock(multiprocessing.Process):
    times = 0
    def __init__(self,inter):
        multiprocessing.Process.__init__(self)
        self.inter = inter
        Clock.times += 1

    def run(self):
        print id(1)
        while 1:
            print "time%s is %s"%(self.inter,time.ctime())
            time.sleep(self.inter)

if __name__ == "__main__":
    '''不能在idle运行,在dos下运行,将以下注释打开/关闭运行结果是不一样的'''
    a = Clock(1)
    b = Clock(2)
    '''daemon,True = 主进程终止,子进程终止,False = 主进程终止,子进程不会终止'''
    #a.daemon = True
    print a.daemon 
    print b.daemon
    a.start()
    b.start()
    time.sleep(5)
    #b.join()
    print id(1)    #不同进程内,地址是不同的
    print 'finish'

Queue

multiprocessing模块支持的进程间通信的方式:管道和队列。这两种方法都是使用消息传递实现的。

Queue([size])

创建一个共享的进程队列

size为队列的最大长度,默认为无大小限制。

底层使用管道,锁和信号量实现。利用线程将队列中的数据传输到管道。

When a process first puts an item on the queue a feeder thread is started which transfers objects from a buffer into the pipe.

Queue的实例q具有以下方法。

  1. q.qsize()

    返回队列中成员的数量

    但是此结果并不可靠,因为多线程和多进程,在返回结果和使用结果之间,队列中可能添加/删除了成员。

  2. q.empty()

    如果调用此方法时,q为空,返回True

    但是此结果并不可靠,因为多线程和多进程,在返回结果和使用结果之间,队列中可能添加/删除了成员。

  3. q.full()

    如果调用此方法时,q已满,返回True

    但是此结果并不可靠,因为多线程和多进程,在返回结果和使用结果之间,队列中可能添加/删除了成员。

  4. q.put(obj[, block[, timeout]])

    将obj放入队列中。如果队列已满,此方法将阻塞至队列有空间可用为止

    block,控制阻塞行为,默认为True;如果设置为False,将obj放入队列时,如果没有可用空间的话,将引发Queue.Full异常

    timeout为阻塞时间,超时后将引发Queue.Full异常。默认为无限制等待。

  5. q.put_nowait(obj)

    等价于q.put(obj,False)

  6. q.get([block[, timeout]])

    返回q中的一个成员。如果队列为空,此方法将阻塞至队列中有成员可用为止

    block控制阻塞行为,默认为True;如果设置为False,如果队列中没有可用成员,将引发Queue.Empty异常

    timeout为阻塞的时间,超时后将引发Queue.Empty异常。默认为无限制等待。

  7. q.get_nowait()

    等价于q.(get,False)

  8. q.close()

    关闭队列,防止队列中放入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,待这些数据写入完成后将马上关闭队列

    q在被垃圾回收时将调用此方法

    q被关闭后,q.get()可以正常使用,q.put(),q.qsize(),q.empty(),q.full()等操作会抛出异常

  9. q.join_thread()

    此方法用于q.close()后,等待后台线程运行完成,阻塞主线程至后台线程运行结束,保证缓冲区中的数据放入管道

    调用q.cancel_join_thread()可禁止此行为。

    Join the background thread. This can only be used after close() has been called. It blocks until the background thread exits, ensuring that all data in the buffer has been flushed to the pipe.

    By default if a process is not the creator of the queue then on exit it will attempt to join the queue’s background thread. The process can call cancel_join_thread() to make join_thread() do nothing.

  10. q.cancel_join_thread()

    阻止q.join_thread()阻塞主线

    Prevent join_thread() from blocking. In particular, this prevents the background thread from being joined automatically when the process exits

code

#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing,time
class Consumer(multiprocessing.Process):
    def __init__(self,queue,lock):
        multiprocessing.Process.__init__(self)
        self.queue = queue
        self.lock = lock

    def run(self,):
        times = 5
        while times:
            times -= 1
            i = self.queue.get()
            self.lock.acquire()
            print 'get = %s, %s'%(i,type(i))
            self.lock.release()

class Producer(multiprocessing.Process):
    def __init__(self,queue,lock):
        multiprocessing.Process.__init__(self)
        self.queue = queue
        self.lock = lock

    def run(self,):
        times = 6
        while times:
            times -= 1
            self.queue.put(times)
            self.lock.acquire()
            print 'put = %s'%(times)
            self.lock.release()
            time.sleep(2)

if __name__ == "__main__":
    '''不能在idle运行,在dos下运行'''
    q = multiprocessing.Queue(5)
    lock = multiprocessing.Lock()

    a = Consumer(q,lock)
    a.start()

    b = Producer(q,lock)
    b.start()

    for i in range(2):
        lock.acquire()
        print q.empty(),q.full(),q.qsize()
        lock.release()
        time.sleep(0.5)

    q.close()
    #q.join_thread()
    q.cancel_join_thread()
    try:
        q.put(1)
    except Exception,e:
        lock.acquire()
        print 'put Exception',e
        lock.release()

    try:
        print q.empty(),q.full(),q.qsize()
    except Exception,e:
        lock.acquire()
        print 'else',e
        lock.release()

    print 'join'
    a.join()
    b.join()
    print 'join finish'

JoinableQueue

Queue的子类,比Queue多了task_done()和join()方法。

队列允许成员的使用者通知生产者成员已经被成功取出。

通知进程使用共享的信号和条件变量实现。

JoinableQueue的实例q除了与Queue的实例相同的方法外,还具有以下方法

  1. q.task_done()

    使用者使用此方法发出信号,表示q.get()的成员已经被成功处理

    如果调用此方法的次数大于队列中的删除的成员数量,将引发ValueError异常

  2. q.join()

    生产者使用此方法进行阻塞,阻塞至队列中的每个成员均调用了q.task_done()为止

code

#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing,time

class Consumer(multiprocessing.Process):
    def __init__(self,queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue

    def run(self,):
        while 1:
            info = self.queue.get()
            print 'pid = %s,info = %s'%(self.pid,info)
            self.queue.task_done()
            if info == None:
                break
            #self.queue.task_done()  #注释打开,报错,调用次数太多
            time.sleep(1)
        print 'pid = %s over'%(self.pid)

if __name__ == "__main__":
    '''不能在idle运行,在dos下运行'''
    q = multiprocessing.JoinableQueue()
    '''两个进程轮流从队列中取数据'''
    a = Consumer(q)
    a.start()
    a = Consumer(q)
    a.start()
    for i in range(18):
        print 'put %s'%i
        q.put(i)
        if (i+1) % 3 == 0:  #三个一组,如果队列不为空,则等待至队列为空
            q.join()
    q.put(None)
    q.put(None)
    q.join()                #保证队列中的数据都被处理完
    print 'join finish'

Pipe

管道,进程间消息传递的一种形式。

Pipe([duplex])

在进程之间创建一条管道(用于具有亲缘关系进程间的通信),返回元组(conn1,conn2)

conn1,conn2表示管道两端的Connection对象。默认情况下,管道是双向的,如果将duplex置为False,conn1只能用于接收,conn2只能用于发送。

如果某个Process使用了管道,Pipe必须在Process对象之前创建。

Connection

用于发送和接收报文或picklable objects。Connection对象通常使用Pipe()创建。

Connection的实例c具有的属性

  1. c.send(obj)

    向管道另一端的Connection对象发送消息

    The obj must be picklable. Very large pickles (approximately 32 MB+, though it depends on the OS) may raise a ValueError exception.

  2. c.recv()

    接收Connection对象发送的消息。如果没有消息可接收,将阻塞至有消息为止

    如果连接的另一端已经关闭,并且没有可接收的对象,将引发EOFError异常。

  3. c.fileno()

    返回连接使用的文件描述符

  4. c.close()

    关闭连接。

    如果c被垃圾回收机制处理了,将调用该方法。

  5. c.poll([timeout])

    返回是否有可读的可用数据

    timeout为等待的最长时限。默认为立即返回结果,如果将timeout置为None,将无限期等待

  6. c.send_bytes(buffer[, offset[, size]])

    buffer,支持缓冲区接口的对象

    offset,指定缓冲区中的字节偏移量

    size,发送字节数

  7. c.recv_bytes([maxlength])

    maxlength,指定接收的最大字节数,如果接收到的消息超过了这个最大值,将引发IOError异常。如果连接的另一端已经关闭,并且没有可接收的对象,将引发EOFError异常。

  8. c.recv_bytes_into(buffer[, offset])

    接收字节消息,并将其存储在buffer对象中

    offset为缓冲区中放置消息的字节偏移。如果接收到的消息超过了缓冲区长度,将引发BufferTooShort异常

管道由操作系统进行引用计数的,必须在所有进程中关闭管道才能生成EOFError异常。因此只在生产者中关闭管道是不会有效果的,消费者也必须关闭相同的管道才会起作用。

也就是说单方关闭管道没有任何效果,必须全部进程关闭该管道才会起作用。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing,time
class Consumer(multiprocessing.Process):
    def __init__(self,out,in_put,time):
        multiprocessing.Process.__init__(self)
        self.recv = recv
        self.sleep = time

    def run(self):
        while 1:
            #print 'waiting...'
            item = self.recv.recv()
            if item == None:
                self.recv.close()
                break
            print 'pid = %s,item = %s'%(self.pid,item)
            time.sleep(self.sleep)  
        print 'pid = %s over'%(self.pid)

if __name__ == "__main__":
    '''不能在idle运行,在dos下运行'''
    '''recv只能接受,send只能发送'''
    recv,send = multiprocessing.Pipe(False)
    a = Consumer(recv,send,0)
    a.start()
    a = Consumer(recv,send,1)
    a.start()
    recv.close()
    for i in range(10) + [None,None]:
        send.send(i)
    send.close()
    print 'send close'
    a.join()
    print 'join finish'

进程池,Pool

Pool([num_processes[, initializer[, initargs[, maxtasksperchild]]]])

创建进程池。

num_processes,表示工作进程个数,默认为None,表示worker进程数为cpu_count()(CPU数量)

initializer,表示工作进程启动时调用的初始化函数

initargs,表示initializer函数的参数,如果initializer不为None,在每个工作进程start之前会调用initializer(initargs)

maxtaskperchild,表示每个工作进程在退出/被其他新的进程替代前,需要完成的工作任务数,默认为None,表示工作进程存活时间与pool相同,即不会自动退出/被替换。

当有进程退出时,进程池会创建新的进程,保证进程池中的进程数不变(进程号不会变…)。

进程池内部由多个线程互相协作。

进程池对象的方法只能由创建进程池的进程使用

Worker processes within a Pool typically live for the complete duration of the Pool’s work queue. A frequent pattern found in other systems (such as Apache, mod_wsgi, etc) to free resources held by workers is to allow a worker within a pool to complete only a set amount of work before being exiting, being cleaned up and a new process spawned to replace the old one. The *maxtasksperchild*argument to the Pool exposes this ability to the end user.

Pool的实例p具有的属性

  1. p.apply(func[, args[, kwds]])

    func,表示在进程池中执行的函数

    args、kwds分别表示func的位置参数和关键字参数

    与内嵌的apply()函数类似,父进程会被阻塞至func执行结束。

    apply()调用的是apply_async(),只不过没有返回AsyncResult的实例,而是等待func执行结束,返回func的执行结果。

    每次只能向进程池分配一个任务

  2. p.apply_async(func[, args[, kwds[, callback]]])

    func,表示在进程池中执行的函数

    args、kwds分别表示func的位置参数和关键字参数

    callback为一个单参数的方法,当结果返回时,callback方法会被调用,参数即为任务执行后的结果,callback禁止执行任何阻塞操作,否则将阻塞接收其他异步操作的结果

    p.apply()的变种,采用非阻塞(异步)的调用方式,异步地执行函数,返回值为AsyncResult的实例,可用于函数的返回值

    每次只能向进程池分配一个任务

  3. p.map(func, iterable[, chunksize])

    func,表示在进程池中执行的函数
    iterable,func的参数序列
    chunksize,表示将iterable序列按每组chunksize的大小进行分割,每个分割后的序列提交给进程池中的一个进程处理

    注:it supports only one *iterable* argument though

    与内置的map函数基本一致,map为map_async的阻塞版本,直接阻塞至函数全部返回

    一次可分配多个任务到进程池中

  4. p.map_async(func, iterable[, chunksize[, callback]])

    func,表示执行此任务的方法
    iterable,表示任务参数序列
    chunksize,表示将iterable序列按每组chunksize的大小进行分割,每个分割后的序列提交给进程池中的一个任务进行处理
    callback,表示一个单参数的方法,当有结果返回时,callback方法会被调用,参数即为任务执行后的结果

    同map函数,但是该方法为非阻塞(异步),返回值为AsyncResult的实例,稍后可用于获得结果。

    一次可分配多个任务到进程池中

  5. p.imap(func, iterable[, chunksize])

    与map相同,只不过返回迭代器

  6. p.imap_unordered(func, iterable[, chunksize])

    同imap,返回结果是无序的

  7. p.terminate()

    立即终止进程池中的所有进程,同时不执行任何清理或结束任何挂起工作。

    如果p被垃圾回收时,将自动调用此函数。

  8. p.close()

    关闭进程池(pool),使其不在接受新的任务,如果仍有任务在执行,将等待其执行结束

  9. p.join()

    主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用

AsyncResult

The class of the result returned by Pool.apply_async() and Pool.map_async().

  1. p.get([timeout])

    返回结果,如果有必要则等待结果

    timeout为等待时间,如果在指定的时间内没有等到结果,将引发异常。

  2. p.wait([timeout])

    等待结果变为可用,即任务执行完成

  3. p.ready()

    如果调用完成,返回True

  4. p.successful()

    表示整个调用执行状态,如果进程仍在执行,则抛出AssertionError异常。

#coding: utf-8
import multiprocessing
import time
def func(msg):
    print "msg:%s,time = %s"%(msg,time.ctime())
    time.sleep(1)

if __name__ == "__main__":
    '''apply_async与apply区别'''
    pool = multiprocessing.Pool(processes = 3)
    t1 = time.time()
    for i in xrange(4):
        msg = "hello %d" %(i)
        pool.apply_async(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
    pool.close()
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    t2 = time.time()
    print u"异步执行时间",t2 - t1

    pool = multiprocessing.Pool(processes = 3)
    t1 = time.time()
    for i in xrange(4):
        msg = "hello %d" %(i)
        pool.apply(func, (msg, ))
    pool.close()
    pool.join()  
    t2 = time.time()
    print u"同步执行时间",t2 - t1
#!/usr/bin/python
# -*- coding: utf-8 -*-

import time,os
from multiprocessing import Pool
def run(a):
  #print os.getpid(),time.ctime()
  time.sleep(1)
  return a * 2

if __name__ == "__main__":
  l = range(10)    
  pool = Pool(5)  #创建拥有5个进程数量的进程池
  e1 = time.time()
  '''程序中的r1表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,
  这个结果存在一个列表中,也就是一个结果堆中,实际上是用了队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。'''
  r = pool.map(run, l)
  print 'pass time = %s'%(time.time() - e1)
  print u"map exe time = %s"%(time.time()-e1)
  print r

  print "======================="
  e1 = time.time()
  '''程序中的r1表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,
  这个结果存在一个列表中,也就是一个结果堆中,实际上是用了队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。'''
  r = pool.map_async(run,l)
  print type(r),r.ready()
  print 'pass time = %s'%(time.time() - e1)
  r.wait()
  print r.ready()
  e2 = time.time()
  print u"map_async exe time = %s"%(e2-e1)
  print r
  print r.get()

  pool.close()#关闭进程池,不再接受新的进程
  pool.join() #主进程阻塞等待子进程的退出

共享数据与同步

通常,进程之间是相互独立的。但是通过共享内存(nmap模块),进程之间可以共享对象,使多个进程可以访问同一个变量(地址相同,变量名可能不同)。

多进程共享资源必然会导致进程间相互竞争,所以应该尽最大可能防止使用共享状态。

It is possible to create shared objects using shared memory which can be inherited by child processes.

Value

Value((typecode_or_type, args[, lock])

Return a ctypes object allocated from shared memory. By default the return value is actually a synchronized wrapper for the object.

在共享内存中创建ctypes()对象

typecode_or_type,决定返回对象的类型,array模块使用的类型代码(如’i’,’d’等)或者ctypes模块的类型对象。

args,传递给typecode_or_type构造函数的参数

lock,默认为True,创建一个互斥锁来限制对Value对象的访问,如果传入一个锁,如Lock或RLock的实例,将用于同步。如果传入False,Value的实例就不会被锁保护,它将不是进程安全的。

Array()

Array(typecode_or_type, size_or_initializer, **kwds)

Returns a synchronized shared array

Array类型

Type code     C Type           Minimum size in bytes 
'c'         character            1 
'b'         signed integer       1 
'B'         unsigned integer     1 
'u'         Unicode character    2 
'h'         signed integer       2 
'H'         unsigned integer     2 
'i'         signed integer       2 
'I'         unsigned integer     2 
'l'         signed integer       4 
'L'         unsigned integer     4 
'f'         floating point       4 
'd'         floating point       8 
#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing
def f(n, a):
    n.value   = 3.14
    a[0]      = 5

if __name__ == '__main__':
    num   = multiprocessing.Value('d', 0.0)
    arr   = multiprocessing.Array('i', range(10))
    p = multiprocessing.Process(target=f, args=(num, arr))
    p.start()
    p.join()
    print num.value
    print arr[:]

同步

以下为multiprocessing模块提供的同步原语

原语描述
Lock互斥锁
RLock可重入的互斥锁(同一个进程可以多次获得它,同时不会造成阻塞)
Semaphore信号量
BoundedSemaphore有边界的信号量
Event事件
Condition条件变量

托管对象

和线程不同,进程不支持共享对象。尽管可以利用共享内存创建共享值和数组,但是对Python对象不起作用。

multiprocessing模块提供了一种共享Python对象的途径,但是它们得在管理器的控制下。

管理器是独立运行的子进程,其中存在真实的对象,并以服务器的形式运行,其他进程通过使用代理访问共享对象,这些代理作为客户端运行。

使用简单托管对象的最直观方式就是Manager()函数。

Manager()

BaseManager的子类,返回一个启动的SyncManager()实例,可用于创建共享对象并返回访问这些共享对象的代理。

Returns a started SyncManager object which can be used for sharing objects between processes. The returned manager object corresponds to a spawned child process and has methods which will create shared objects and return corresponding proxies.

BaseManager

BaseManager([address[, authkey]]),创建管理器服务器的基类

address = (hostname,port),指定服务器的网址地址,默认为简单分配一个空闲的端口

authkey,连接到服务器的客户端的身份验证,默认为current_process().authkey的值

BaseManager的实例m具有以下属性

  1. start([initializer[, initargs]])

    启动一个单独的子进程,并在该子进程中启动管理器服务器

  2. get_server()

    获取服务器对象

  3. connect()

    连接管理器对象

  4. shutdown()

    关闭管理器对象,只能在调用了start()方法之后调用

  5. address

    只读属性,管理器服务器正在使用的地址

SyncManager

SyncManager的实例m具有以下属性

以下类型均不是进程安全的,需要加锁..

  1. Array(self,*args,**kwds)
  2. BoundedSemaphore(self,*args,**kwds)
  3. Condition(self,*args,**kwds)
  4. Event(self,*args,**kwds)
  5. JoinableQueue(self,*args,**kwds)
  6. Lock(self,*args,**kwds)
  7. Namespace(self,*args,**kwds)
  8. Pool(self,*args,**kwds)
  9. Queue(self,*args,**kwds)
  10. RLock(self,*args,**kwds)
  11. Semaphore(self,*args,**kwds)
  12. Value(self,*args,**kwds)
  13. dict(self,*args,**kwds)
  14. list(self,*args,**kwds)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing
def f(x, arr, l,d,n):
    x.value = 3.14
    arr[0] = 5
    l.append('Hello')
    d[1] = 2
    n.a = 10

if __name__ == '__main__':
    server = multiprocessing.Manager()
    x    = server.Value('d', 0.0)
    arr  = server.Array('i', range(10))
    l    = server.list()
    d    = server.dict()
    n    = server.Namespace()

    proc = multiprocessing.Process(target=f, args=(x, arr, l,d,n))
    proc.start()
    proc.join()

    print x.value
    print arr
    print l
    print d
    print n
#!/usr/bin/python
# -*- coding: utf-8 -*-
import multiprocessing,time
def watch(d,event):
    while 1:
        event.wait()
        print d
        event.clear()

if __name__ == "__main__":
    m = multiprocessing.Manager()
    d = m.dict()
    event = m.Event()

    p = multiprocessing.Process(target = watch,args = (d,event))
    p.daemon = True
    p.start()

    d['1'] = 1
    event.set()
    time.sleep(1)

    d['2'] = 2
    event.set()
    time.sleep(1)
    p.terminate()
    m.shutdown()
    print "finish"
#!/usr/bin/python
# -*- coding: utf-8 -*-

import multiprocessing,time

def add(d,times,l):
    while times > 0:
        times -= 1
        l.acquire()
        d.value += 1
        l.release()

if __name__ == "__main__":
    '''验证Value等共享对象是不是进程安全的,结果不是进程安全的,如果将锁去掉,结果不等于20000'''
    m = multiprocessing.Manager()
    d = m.Value('d', 0.0)
    l = m.Lock()
    times = 10000
    p = multiprocessing.Process(target = add,args = (d,times,l))
    p.daemon = True
    p.start()
    while times > 0:
        times -= 1
        l.acquire()
        d.value += 1
        l.release()
    p.join()
    print d
    m.shutdown()   #如果不用了,关闭服务器端- -
    print "finish"

使用多进程的建议

  1. 最好阅读官方文档,官方文档更全面;
  2. 确保进程间传递的所有数据都能够序列化;
  3. 尽可能的避免使用共享数据,尽可能使用消息传递和队列。使用消息传递时,不必担心同步等问题。当进程数增加时,还能提供更好的扩展;
  4. 注意关闭进程的方式,需要显式地关闭进程,并使用一种良好的终止模式,而不要仅仅依赖垃圾收集或被迫使用terminate()操作强制终止子进程;
  5. 管理器和代理的使用与分布式计算密切相关;
  6. multiprocessing源于pyprocessing的第三方库;
  7. 此模块更适用于UNIX系统;
  8. 最重要的一点,尽量让事情变得简单;

练习code

#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import *
import threading,multiprocessing,time

def recv(queue,var):
    while True:
        s = queue.get()
        var.set(s)
        if s == 10000:
            break
    print 'recv over',id(5000)

def send(queue,start,end):
    for i in range(start,end):
        #time.sleep(1)
        print "put %s"%i
        queue.put(i)
    print "send over",id(5000)

if __name__ == "__main__":
    root = Tk()
    root.title("hello world")
    root.geometry('300x100')
    var = StringVar()
    e = Entry(root, textvariable = var)
    var.set("hello")
    e.pack(side = TOP)

    l = []
    queue = multiprocessing.Queue(5)
    p = multiprocessing.Process(target = send,args = (queue,10,5000))
    p.daemon = True
    p.start()

    p = multiprocessing.Process(target = send,args = (queue,5000,10001))
    p.daemon = True
    p.start()

    thread = threading.Thread(target = recv,args = (queue,var))
    thread.daemon = True
    thread.start()

    queue.put('a')
    root.mainloop()
    queue.close()
    p.join()
    thread.join()
    print 'over',l,id(5000)

转载请标明出处,原文地址(http://blog.csdn.net/lis_12/article/details/54564703).

如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。

参考网址

官方文档

  1. https://docs.python.org/2/library/multiprocessing.html#multiprocessing.Process.start
  2. https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled

同步,异步,阻塞,非阻塞

  1. http://blog.csdn.net/lis_12/article/details/54381279

GIL锁

  1. http://blog.csdn.net/i2cbus/article/details/23555063
  2. http://cenalulu.github.io/python/gil-in-python/
  3. http://www.dabeaz.com/python/UnderstandingGIL.pdf

进程池源码分析

  1. http://www.cnblogs.com/Tour/p/4537212.html
  2. http://www.cnblogs.com/Tour/p/4564710.html
  3. http://www.cnblogs.com/Tour/p/4722688.html

参考书籍

  1. Python参考手册(第四版)
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值