Python多进程 - logging.FileHandler对象引发PicklingError

1 投票
1 回答
3014 浏览
提问于 2025-04-18 13:24

看起来,logging模块中的处理器和multiprocessing的工作不太兼容:

import functools
import logging
import multiprocessing as mp

logger = logging.getLogger( 'myLogger' )
handler = logging.FileHandler( 'logFile' )

def worker( x, handler ) :
    print x ** 2

pWorker = functools.partial( worker, handler=handler )

#
if __name__ == '__main__' :
    pool = mp.Pool( processes=1 )
    pool.map( pWorker, range(3) )
    pool.close()
    pool.join()

输出:

cPickle.PicklingError: Can't pickle <type 'thread.lock'>: attribute lookup thread.lock failed

如果我把pWorker换成以下任意一种方法,就不会出现错误了:

# this works
def pWorker( x ) :
    worker( x, handler )

# this works too
pWorker = functools.partial( worker, handler=open( 'logFile' ) )

我其实不太明白PicklingError是什么。是不是因为logging.FileHandler这个类的对象不能被“打包”?(我在网上查过,但没找到相关信息)

1 个回答

2

FileHandler对象内部使用了一个叫threading.Lock的东西来确保多个线程在写入时不会互相干扰。不过,threading.Lock返回的thread.lock对象不能被“打包”,这就意味着它不能在不同的进程之间传递,而这正是通过pool.map把它传给子进程所需要的。

multiprocessing的文档中,有一部分专门讲了如何在使用multiprocessing时进行日志记录,你可以在这里找到。简单来说,你需要让子进程继承父进程的日志记录器,而不是试图通过调用map来显式地传递日志记录器或处理器。

不过需要注意的是,在Linux上,你可以这样做:

from multiprocessing import Pool
import logging

logger = logging.getLogger( 'myLogger' )


def worker(x):
    print handler
    print x **2 

def initializer(handle):
    global handler
    handler = handle

if __name__ == "__main__":
    handler = logging.FileHandler( 'logFile' )
    #pWorker = functools.partial( worker, handler=handler )
    pool = Pool(processes=4, initializer=initializer, initargs=(handler,))
    pool.map(worker, range(3))
    pool.close()
    pool.join

initializerinitargs用于在每个子进程启动时运行一个方法。由于os.fork的工作方式,这在Linux上允许处理器通过继承的方式进入子进程。但是,在Windows上是行不通的;因为Windows不支持os.fork,所以仍然需要将handler打包才能通过initargs传递。

撰写回答