Python多进程模块multiprocessing

概述:

  Python中的多进程由multiprocessing模块提供,multiprocessing模块中通过Process和Pool两个类提供多进程服务,除了这两个类以外,还提供了进程锁(RLockLockEventCondition和 Semaphore)和进程间数据共享(Array、Manager和queues的Queue类)等工具;

  由于在Linux中,每一个子进程的数据都是由父进程提供的,每启动一个子进程都克隆一份父进程数据,这样以来,当程序创建大量进程时会消耗大量系统资源,导致系统资源耗尽,此时就出现了multiprocessing模块中Process和Pool两个类,具体这两个类的功能在后续中介绍;

Process类:

Process类的实现是基于fork机制,因此不被windows平台支持

一、使用Process创建多线程:

import os
import multiprocessing

def foo(i):
    # 同样的参数传递方法
    print("这里是 ", multiprocessing.current_process().name)
    print('模块名称:', __name__)
    print('父进程 id:', os.getppid())  # 获取父进程id
    print('当前子进程 id:', os.getpid())  # 获取自己的进程id
    print('------------------------')

if __name__ == '__main__':

    for i in range(5):
        p = multiprocessing.Process(target=foo, args=(i,))
        p.start()

输出结果:

这里是  Process-2
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 5260
--------------
这里是  Process-3
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 4912
--------------
这里是  Process-4
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 5176
--------------
这里是  Process-1
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 5380
--------------
这里是  Process-5
模块名称: __mp_main__
父进程 id: 880
当前子进程 id: 3520
--------------

二、进程池Pool类:

上面讲到:程序过多的创建进程导致系统资源过多的被占用,影响系统运行,此时就是体现Pool类的重要性的时候了,通常我们在使用C语言编程时会创建一个进程池,在这里Python直接封装了这个进程池,方便了我们使用,Pool类就是为程序创建一定数目的进程,进程池内部维护了一个进程序列,需要时就去进程池中拿取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池常用的方法:

  • apply() 同步执行(串行)
  • apply_async() 异步执行(并行)
  • terminate() 立刻关闭进程池
  • join() 主进程等待所有子进程执行完毕。必须在close或terminate()之后。
  • close() 等待所有进程结束后,才关闭进程池
def fun_test(time_f):
    #print "pid %s start at %s :", os.gitpid(),time.
	time.sleep(time_f)
	print "pid num is :", os.getpid()

if __name__ == '__main__':
	pool = multiprocessing.Pool(2)
	amount = raw_input("Please input pages :")
	for i in range(int(amount)):
			pool.apply_async(fun_test, (time_t, ))
	pool.close()
	pool.join()

输出结果:

Please input pages :2
pid num is : 20149
pid num is : 20150

两个进程异步进行,当执行上述代码是可以看到两个进程同时结束;需要注意的是程序需要先调用pool.close关闭从进程池中继续启用进程,然后调用pool.join等待子进程全部结束;

三、进程间的数据共享

在Linux中,每个子进程的数据都是由父进程提供的,每启动一个子进程就从父进程克隆一份数据。

创建一个进程需要非常大的开销,每个进程都有自己独立的数据空间,不同进程之间通常是不能共享数据的,要想共享数据,一般通过中间件来实现。

3.1使用Array共享数据

对于Array数组类,括号内的“i”表示它内部的元素全部是int类型,而不是指字符“i”,数组内的元素可以预先指定,也可以只指定数组的长度。Array类在实例化的时候必须指定数组的数据类型和数组的大小,类似temp = Array('i', 5)。对于数据类型有下面的对应关系:

'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

下面是一个多进程修改同一个数组的例子:

from multiprocessing import Process
from multiprocessing import Array

def func(i,temp):
    temp[0] += 100
    print("进程%s " % i, ' 修改数组第一个元素后----->', temp[0])

if __name__ == '__main__':
    temp = Array('i', [1, 2, 3, 4])
    for i in range(10):
        p = Process(target=func, args=(i, temp))
        p.start()

运行结果:

进程2   修改数组第一个元素后-----> 101
进程4   修改数组第一个元素后-----> 201
进程5   修改数组第一个元素后-----> 301
进程3   修改数组第一个元素后-----> 401
进程1   修改数组第一个元素后-----> 501
进程6   修改数组第一个元素后-----> 601
进程9   修改数组第一个元素后-----> 701
进程8   修改数组第一个元素后-----> 801
进程0   修改数组第一个元素后-----> 901
进程7   修改数组第一个元素后-----> 1001

3.2、使用Manager共享数据

  通过Manager类也可以实现进程间数据的共享,Manager()返回的manager对象提供一个服务进程,使得其他进程可以通过代理的方式操作Python对象。manager对象支持

listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValue ,Array等多种格式

from multiprocessing import Process
from multiprocessing import Manager

def func(i, dic):
    dic["num"] = 100+i
    print(dic.items())

if __name__ == '__main__':
    dic = Manager().dict()
    for i in range(10):
        p = Process(target=func, args=(i, dic))
        p.start()
        p.join()

运行结果:

[('num', 100)]
[('num', 101)]
[('num', 102)]
[('num', 103)]
[('num', 104)]
[('num', 105)]
[('num', 106)]
[('num', 107)]
[('num', 108)]
[('num', 109)]

3.3使用queues的Queue类共享数据

multiprocessing的queues模块,提供了一个Queue队列类,可以实现进程间的数据共享,如下例所示:

import multiprocessing
from multiprocessing import Process
from multiprocessing import queues

def func(i, q):
    ret = q.get()
    print("进程%s从队列里获取了一个%s,然后又向队列里放入了一个%s" % (i, ret, i))
    q.put(i)

if __name__ == "__main__":
    lis = queues.Queue(20, ctx=multiprocessing)
    lis.put(0)
    for i in range(10):
        p = Process(target=func, args=(i, lis,))
        p.start()

运行结果:

进程1从队列里获取了一个0,然后又向队列里放入了一个1
进程4从队列里获取了一个1,然后又向队列里放入了一个4
进程2从队列里获取了一个4,然后又向队列里放入了一个2
进程6从队列里获取了一个2,然后又向队列里放入了一个6
进程0从队列里获取了一个6,然后又向队列里放入了一个0
进程5从队列里获取了一个0,然后又向队列里放入了一个5
进程9从队列里获取了一个5,然后又向队列里放入了一个9
进程7从队列里获取了一个9,然后又向队列里放入了一个7
进程3从队列里获取了一个7,然后又向队列里放入了一个3
进程8从队列里获取了一个3,然后又向队列里放入了一个8

关于queue和Queue,在Python库中非常频繁的出现,很容易就搞混淆了。甚至是multiprocessing自己还有一个Queue类(大写的Q),一样能实现queues.Queue的功能,导入方式是from multiprocessing import Queue

参考资料:

http://www.liujiangblog.com/course/python/82

将的很详细,也请大家关注他的其他文章,写的都很好;

猜你喜欢

转载自blog.csdn.net/likunshan/article/details/81707632