注意:python多进程的机制应该是把每个进程要调用的方法和传入的参数(如上面例子中的ProcessWorker)编译然后打包,然后复制到每个进程中执行。如果输入的是一个有界函数,那么它的参数应该是它所属的类(包括参数和方法),但是这是无法获得的,而且类属性和方法可能会有坑,导致难以打包。所以python限定了多进程要调用的函数不能是类方法。
我们要把多进程调用的函数放到类外面,或者变成静态函数。但是静态函数的话不能被所属的类的方法调用(self.ProcessWorker的形式),需要在外部调用,如mc=MyClass(),mc.ProcessWorker来调用,或者MyClass().ProcessWorker来调用。
python多进程编程使用进程池非常的方便管理进程,但是有时候子进程之间会抢占一些独占资源,比如consol或者比如日志文件的写入权限,这样的时候我们一般需要共享一个Lock来对独占资源加锁。lock作为一个不可直接打包的资源是没有办法作为一个参数直接给Pool的map方法里的函数传参的。为了解决这个问题,有两种解决方法,一种是使用多进程的管理器Manager(),并使用偏函数的办法传递对象Manager.Lock()。第二种是在进程池创建时传递multiprocessing.Lock()对象。
下面以一个具体的栗子来说明。
比如我现在有一个数据列表我想通过多进程的方式将里面的数据发送到指定的API并且在日志文件中记录每次请求所用的时间。
我们最容易想到的解决办法就是把锁作为一个参数传进去:
from multiprocessing import Pool, Lock
import urllib2
from time import clock
from functools import partial
def send_request(lock, data):
api_url = 'http://api.xxxx.com/?data=%s'
start_time = clock()
print urllib2.urlopen(api_url % data).read()
end_time = clock()
lock.acquire()
whit open('request.log', 'a+') as logs:
logs.write('request %s cost: %s\n' % (data, end_time - start_time))
lock.release()
if __name__ == '__main__':
data_list = ['data1', 'data2', 'data3']
pool = Pool(8)
lock = Lock()
partial_send_request(send_request, lock=lock)
pool.map(partial_send_request, data_list)
pool.close()
pool.join()
在这样的情况下,lock作为一个不可直接打包的资源是没有办法作为一个参数直接给Pool的map方法里的函数传参的。
会出现一个运行时错误:
Runtime Error: Lock objects should only be shared between processes through inheritance.
根据一开始的思路我们可以把代码改成下面的样子:
第一种思路,使用Manager。
send_request函数不用改变,只改变main中的内容:
if __name__ == '__main__':
from multiprocessing import Manager
data_list = ['data1', 'data2', 'data3']
pool = Pool(8)
manager = Manager()
lock = manager.Lock()
partial_send_request(send_request, lock=lock)
pool.map(partial_send_request, data_list)
pool.close()
pool.join()
这是第一种方法,但是对于仅仅需要一个日志写入锁就用一个Manager显的十分重了。这种方式其实是需要一个专门的进程去处理Manager服务。所有的加锁和释放锁的操作都是通过IPC传递给Manager服务的。
第二种解决思路就是通过initializer参数在Pool对象创建时传递Lock对象。这种方式将Lock对象变为了所有子进程的全局对象。
代码可以作如下修改:
def send_request(data):
api_url = 'http://api.xxxx.com/?data=%s'
start_time = clock()
print urllib2.urlopen(api_url % data).read()
end_time = clock()
lock.acquire()
whit open('request.log', 'a+') as logs:
logs.write('request %s cost: %s\n' % (data, end_time - start_time))
lock.release()
def init(l):
global lock
lock = l
if __name__ == '__main__':
data_list = ['data1', 'data2', 'data3']
lock = Lock()
pool = Pool(8, initializer=init, initargs=(lock,))
pool.map(send_request, data_list)
pool.close()
pool.join()