Многопроцесорна обработка на Python - logging.FileHandler обект предизвиква PicklingError

Изглежда, че манипулатори от 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 не могат да се избират? (търсих го в Google, но не намерих нищо)


person usual me    schedule 15.07.2014    source източник


Отговори (1)


Обектът FileHandler вътрешно използва threading.Lock за синхронизиране на записи между нишки. Въпреки това, обектът thread.lock, върнат от threading.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

initializer/initargs се използват за изпълнение на метод веднъж във всеки от дъщерните процеси на пула, веднага щом стартират. В Linux това позволява на манипулатора да бъде в детето чрез наследяване, благодарение на начина, по който работи os.fork. Обаче това няма да работи под Windows; тъй като му липсва поддръжка за os.fork, той все пак ще трябва да изчисти handler, за да го предаде през initargs.

person dano    schedule 15.07.2014