zoukankan      html  css  js  c++  java
  • python多进程-----multiprocessing包

    multiprocessing并非是python的一个模块,而是python中多进程管理的一个包,在学习的时候可以与threading这个模块作类比,正如我们在上一篇转载的文章中所提,python的多线程并不能做到真正的并行处理,只能完成相对的并发处理,那么我们需要的就是python的多进程来完成并行处理,把所有的cpu资源都利用起来。multiprocessing的很大一部分与threading使用同一套API,只不过换到了多进程的环境。这里面要注意,对于多进程来说,win32平台和unix平台差别很大,我们最好在linux上完成实现。

    使用这些共享API时,我们应该注意以下问题(目前这是我能想到的,以后遇到再扩充):

    1、对join的处理

          根据Unix环境高级编程中对进程控制一章的描述,当某个进程fork一个子进程后,该进程必须要调用wait等待子进程结束发送的sigchld信号,对子进程进行资源回收等相关工作,否则,子进程会成为僵死进程,被init收养。所以,在multiprocessing.Process实例化一个对象之后,该对象有必要调用join方法,因为在join方法中完成了对底层wait的处理,源码如下:

        def join(self, timeout=None):
            '''
            Wait until child process terminates
            '''
            assert self._parent_pid == os.getpid(), 'can only join a child process'
            assert self._popen is not None, 'can only join a started process'
            res = self._popen.wait(timeout)
            if res is not None:
                _current_process._children.discard(self)

          不过,调用该方法,要注意join的位置(threading模块有提到),是在每个子进程中阻塞还是在父进程中阻塞,如果在子进程中阻塞可能达不到并行处理的目的,所以要根据具体需求。而对于多线程来说,由于只有一个进程,所有子线程共享同一片内存,所以不是必须要进行join调用。例子如下:

    #!/usr/bin/env python
    __author__ = 'webber'
    import os,time
    import multiprocessing
    
    # worker function
    def worker(sign, lock):
        lock.acquire()
        print sign, 'pid:',os.getpid()
        lock.release()
        time.sleep(1)
    
    # Main
    print 'Main:',os.getpid()
    
    plist = []
    lock = multiprocessing.Lock()
    for j in range(5):
        p = multiprocessing.Process(target=worker,args=('process',lock))
        p.start()
        plist.append(p)
    p.join()
    
    #for process in record:
    #    process.join()

           

          此外,还有一点关于GIL锁的说明,在python多进程中,同样需要全局解释器锁,因为每个子进程都有一把GIL,那么当它们向stdout输出时,可以同时争抢stdout资源,导致在每个子进程输出时会把不同子进程的输出字符混合在一起,无法阅读,影响代码的标志位判断,所以上例子中使用了Lock同步,在一个子进程输出完成之后再允许另一个子进程得到stdout的权限,这样避免了多个任务同时向终端输出。

    2、对IPC的处理

          multiprocessing包与threading模块的另一个差异特性体现在IPC上,python的multiprocessing包自带了对Pipe和Queue的管理,效率上更高,而threading模块需要与Queue模块或os.popen()、subprocess.Popen()等配合使用。
          根据Unix环境高级编程的第15章进程间通信的描述,经典的IPC包括管道、FIFO、消息队列、信号量、以及共享存储。不过应用最多的还是管道。书中指出我们应该把管道看成是半双工的,并且只能在具有公共祖先的两个进程之间使用。
    下面我们用一下Pipe()和Queue()方法:

      a、关于Pipe()

    对照书中给出的底层pipe接口函数,我们看到Pipe方法在Unix平台上实现源码如下:

    def Pipe(duplex=True):
            '''
            Returns pair of connection objects at either end of a pipe
            '''
            if duplex:
                s1, s2 = socket.socketpair()
                s1.setblocking(True)
                s2.setblocking(True)
                c1 = _multiprocessing.Connection(os.dup(s1.fileno()))
                c2 = _multiprocessing.Connection(os.dup(s2.fileno()))
                s1.close()
                s2.close()
            else:
                fd1, fd2 = os.pipe()
                c1 = _multiprocessing.Connection(fd1, writable=False)
                c2 = _multiprocessing.Connection(fd2, readable=False)
    
            return c1, c2

         首先,Pipe可以是单向(half-duplex),也可以是双向的(duplex),默认为双向的。我们可以通过multiprocessing.Pipe(duplex=False)创建单向的管道。该方法返回一个元祖,包含两个文件描述符,如果为单向的,则为(read-only connection,write-only connection);如果为双向的,则为(read-write Connection, read-write Connection)。一个进程从Pipe一端输入对象(fd[1]),然后被Pipe另一端的进程接收(fd[0]),两个进程要有同一个父进程或者其中一个是父进程。单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。这里的双向管道类似于书中提到的“协同进程”的概念。
    例如:

    #!/usr/bin/env python
    import multiprocessing as mul
    
    def proc1(pipe):
    #    pipe.send('hello')
        print 'proc1 rec:',pipe.recv()
    
    def proc2(pipe):
    #    print 'proc2 rec:',pipe.recv()
        pipe.send('hello too')
    
    pipe = mul.Pipe(duplex=False)
    #pipe = mul.Pipe()
    
    p1 = mul.Process(target=proc1,args=(pipe[0],))       # 读管道
    
    p2 = mul.Process(target=proc2,args=(pipe[1],))       # 写管道
    # 由于管道是单向的,对象pipe[0]只有读的权限(recv),而pipe[1]只有写的权限(send)。
    #print pipe
    p1.start()
    p2.start()
    p1.join()
    p2.join()

          b、关于Queue()

         Queue与Pipe相类似,都是先进先出的结构,但Queue允许多个进程放入,多个进程从队列取出对象。这里可以与Queue模块相类比学习。Queue方法其实是Unix环境高级编程IPC中FIFO命名管道的实现方法。FIFO可用于有以下两种情况:
    ---shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件。
    ---客户进程-服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务器进程二者之间传递数据。
    以下就FIFO的第二种情况写一个python例子:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import multiprocessing
    import time
    import os
    
    # 客户进程,向众所周知的FIFO服务器进程发送请求
    def client_proc(queue,msg):
        request = 'I am client ' + str(msg) + '    pid: '+ str(os.getpid()) + '   time:' + str(time.time())        # 注意信息的格式,都统一为字符串类型
        queue.put(request)
    
    def server_proc(queue,lock):
        msg = queue.get()
        lock.acquire()
        print msg + '--------------->I am server ' + 'pid: ' + str(os.getpid())
        lock.release()
    
    plist_cli = []
    plist_ser = []
    lock = multiprocessing.Lock()
    queue = multiprocessing.Queue()  # 参数为空,默认为队列可无限长
    
    for i in range(10):
        p1 = multiprocessing.Process(target=client_proc,args=(queue,i))
        p2 = multiprocessing.Process(target=server_proc,args=(queue,lock))
        p1.start()
        p2.start()
        plist_cli.append(p1)
        plist_ser.append(p2)
    
    for proc in plist_cli:
        proc.join()
        
    for proc in plist_ser:
        proc.join()
        
    queue.close()
    输出如下:
    I am client 2    pid: 9867   time:1482489226.77--------------->I am server pid: 9879
    I am client 0    pid: 9865   time:1482489226.77--------------->I am server pid: 9881
    I am client 4    pid: 9869   time:1482489226.77--------------->I am server pid: 9884
    I am client 1    pid: 9866   time:1482489226.77--------------->I am server pid: 9886
    I am client 3    pid: 9868   time:1482489226.78--------------->I am server pid: 9888
    I am client 7    pid: 9872   time:1482489226.78--------------->I am server pid: 9889
    I am client 5    pid: 9870   time:1482489226.78--------------->I am server pid: 9892
    I am client 6    pid: 9871   time:1482489226.78--------------->I am server pid: 9891
    I am client 9    pid: 9878   time:1482489226.78--------------->I am server pid: 9893
    I am client 8    pid: 9875   time:1482489226.78--------------->I am server pid: 9894

    从输出可以看出,10个客户端进程把生产信息放入队列,10个服务端进程从队列取出信息并且打印,从打印时间和msg的子进程编号来看,10个服务端进程争夺stdout,通过Lock使它们有序输出,不至于输出信息混乱,msg编号没有从0排至9正是因为它们被分配给了不同的cpu资源,不同cpu资源在处理速度上不会完全一样,所以争夺stdout的能力也不同。

     

    3、共享内存和Manager管理

         众所周知,在处理多进程时,每个进程都有自己独立的内存空间,所以在多进程环境中我们应该尽量避免共享资源,否则要依赖与IPC。python的多进程除了上面提到的常用的依赖于管道和FIFO之外,还可以通过共享内存和Manager的方法来共享资源。这个不常用,由于共享内存涉及同步的问题,会降低程序的效率而不推荐使用。以后涉及到再扩展。

    4、进程池

    参考博客:http://www.cnblogs.com/kaituorensheng/p/4465768.html
    当我们在编写网络服务端时,Unix网络编程一书中提到服务端需要fork子进程,用子进程来处理监听到的连接请求,建立连接套接字,并在子进程中关闭监听套接字,父进程中关闭连接套接字。那么,当连接的并发不是很大时,我们可以利用进程池的方式来处理到来的连接。multiprocessing.Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果进程池还没有满,那么就会创建一个新的进程用来执行该请求;如果池中的进程数已经达到最大值,那么该请求将会阻塞等待,直到池中有进程结束,才会创建新的进程来处理该请求。
    Pool方法默认的初始值如下:
    def __init__(self, processes=None, initializer=None, initargs=(),maxtasksperchild=None)
    通常,我们应该指定进程池的大小,如果不指定,默认为cpu的个数,即processes=cpu_count(),我们可以用该模块自带的方法查看本机的cpu个数,

    print multiprocessing.cpu_count()。下面看个进程池的例子:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import multiprocessing
    import time
    
    def func(msg):
        print 'msg:',msg
        time.sleep(3)
        print 'end'
    
    pool = multiprocessing.Pool(processes=3)
    for i in xrange(4):
        msg = 'hello %d' % (i)
        pool.apply_async(func,(msg,))     #非阻塞
    #    pool.apply(func,(msg,))     #阻塞,apply()源自内建函数,用于间接的调用函数,并且按位置把元祖或字典作为参数传入。
    
    #    pool.imap(func,[msg,])      #非阻塞, 注意与apply传的参数的区别
    #    pool.map(func,[msg,])        #阻塞
    
    print 'Mark~~~~~~~~~~~~~~~'
    pool.close()
    pool.join()      # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print 'sub-process done'

    注意apply_async和apply的差别,此外,进程池请求函数处理还可以用map,imap,注意传递参数的区别。

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import multiprocessing
    import time
    
    def func(msg):
        print 'msg:',msg
        time.sleep(3)
        print 'end'
    
    pool = multiprocessing.Pool(50)
    msg = range(50)
    #pool.imap(func,msg)      #非阻塞, 注意与apply传的参数的区别
    pool.map(func,msg)        #阻塞
    
    print 'Mark~~~~~~~~~~~~~~~'
    pool.close()
    pool.join()
    print 'sub-process done'

    此外,如果子进程的处理函数中包含返回值,我们可以在父进程中对子进程调用get方法,将返回值取出,这里注意,要调用get方法的时候,进程池必须采用apply_async调用函数。例如:

    if __name__ == "__main__":
        pool = multiprocessing.Pool(processes=4)
        result = []
        for i in xrange(3):
            msg = "hello %d" %(i)
            result.append(pool.apply_async(func, (msg, )))
        pool.close()
        pool.join()
        for res in result:
            print ":::", res.get()
        print "Sub-process(es) done."

    最后,调用close()之后,进程池不再创建新的进程;

            调用join()之后,wait进程池中的全部进程。必须对Pool先调用close()方法才能join。

    参考博客:http://www.lxway.com/4488626156.htm

  • 相关阅读:
    PowerDesigner使用教程 —— 概念数据模型 (转)
    面板Panel:1.认识Ext.Panel(转)
    批量替换一个数据库中所有表中所有记录
    Ext.data库中几个常用类的原理及其使用 (转)
    也谈C#.NET防止SQL注入式攻击
    jQuery1.3新特性深度分析(Event)
    Sql实现Split
    SQL Server 清除数据库日志脚本(转)
    ExtJS之面向对象编程基本知识
    实战 SQL Server 2005 镜像配置
  • 原文地址:https://www.cnblogs.com/webber1992/p/6217327.html
Copyright © 2011-2022 走看看