技术

Python 进程详解

This post is currently available in Chinese only. The original text follows.Open the Chinese page →

Python 进程

因为GIL的限制,一个进程中只能有一个线程获取到资源,因此在有些场景下会显得很鸡肋。一种解决方法就是利用多进程,通过开多个进程就可以绕开GIL的限制。

多进程

python提供了一个包,multiprocess ,使用方法跟多线程的一样

    from multiprocessing import Process
    
    plist = []
    
    def addProcess(i):
        plist.append(i)
        print 'process %d ----'%i
        print plist
    
    for i in range(5):
        p = Process(target=addProcess,args=(i,))
        p.start()       

结果

    process 0 ----
    [0]
    process 1 ----
    [1]
    process 2 ----
    [2]
    process 3 ----
    [3]
    process 4 ----
    [4]

从结果上面我们能看出来,每个进程都有自己独立的一片数据空间,不会受到其它进程的影响。

多进程的数据共享

虽然多进程能够开辟多个数据空间,但是这些数据空间的数据确实独立的,无法进行共享。因此很多场景下并不能符合我们的要求。好在我们可以通过别的方式进行资源的共享,例如Queue,Array和Manager这三个类来实现功能

Array

    from multiprocessing import Process
    from multiprocessing import Array
    plist = Array('i',10)
    
    def addProcess(i):
        plist[0] += 200
        print 'process %d ----'%i
        print [item for item in plist]
    
    for i in range(5):
        p = Process(target=addProcess,args=(i,))
        p.start()
    
    process 0 ----
    [200, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    process 1 ----
    [400, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    process 2 ----
    [600, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    process 3 ----
    [800, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    process 4 ----
    [1000, 0, 0, 0, 0, 0, 0, 0, 0, 0]

这里我们使用的是multiprocess提供的Array,Array初始化的时候至少传入两个参数,第一个参数为元素类型,这个指的是内部全部元素的类型,第二个参数为初始值或者长度

源码

    def Array(typecode_or_type, size_or_initializer, **kwds):
        '''
        Returns a synchronized shared array
        '''
        from multiprocessing.sharedctypes import Array
        return Array(typecode_or_type, size_or_initializer, **kwds)

元素类型对应表

    ‘c’: ctypes.c_char, ‘u’: ctypes.c_wchar,
    ‘b’: ctypes.c_byte, ‘B’: ctypes.c_ubyte,
    ‘h’: ctypes.c_short, ‘H’: ctypes.c_ushort,
    ‘i’: ctypes.c_int, ‘I’: ctypes.c_uint,
    ‘l’: ctypes.c_long, ‘L’: ctypes.c_ulong,
    ‘f’: ctypes.c_float, ‘d’: ctypes.c_double

Queue

这里的队列就跟多线程中的队列用法是一样的,但是如果不加锁的话,也是会出现一些脏数据的情况

Manager

源码

    def Manager():
        '''
        Returns a manager associated with a running server process
    
        The managers methods such as `Lock()`, `Condition()` and `Queue()`
        can be used to create shared objects.
        '''
        from multiprocessing.managers import SyncManager
        m = SyncManager()
        m.start()
        return m

实例

http://www.jb51.net/article/57663.htm

进程锁

multiprocess中实现了跟多线程中一样的锁,如Lock,Rlock,SEMAPHORE,EVENT,CONDITION http://www.iotxing.com/2018/05/13/159/python-thread/

进程池

既然有线程池,那必然也有进程池。但是,python给我们内置了一个进程池,不需要像线程池那样需要自定义,你只需要简单的from multiprocessing import Pool。

    # -*- coding:utf-8 -*-
    from multiprocessing import Pool
    import time
    
    def f1(args):
        time.sleep(1)
        print(args)
    
    if __name__ == '__main__':
        p = Pool(5)
        for i in range(30):
            p.apply_async(func=f1, args= (i,))
        p.close()           # 等子进程执行完毕后关闭进程池
        # time.sleep(2)
        # p.terminate()     # 立刻关闭进程池
        p.join()

进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有以下几个主要方法:

apply:从进程池里取一个进程并执行

apply_async:apply的异步版本

terminate:立刻关闭进程池

join:主进程等待所有子进程执行完毕。必须在close或terminate之后。

close:等待所有进程结束后,才关闭进程池。