web-dev-qa-db-ja.com

multiprocessing.Pool-PicklingError:<type 'thread.lock'>をpickleできません:属性検索thread.lockが失敗しました

_multiprocessing.Pool_は私を夢中にさせています...
多くのパッケージをアップグレードしたいのですが、パッケージごとに、より大きなバージョンがあるかどうかを確認する必要があります。これは_check_one_関数によって行われます。
メインコードは_Updater.update_メソッドにあります。Poolオブジェクトを作成し、map()メソッドを呼び出します。

コードは次のとおりです。

_def check_one(args):
    res, total, package, version = args
    i = res.qsize()
    logger.info('\r[{0:.1%} - {1}, {2} / {3}]',
        i / float(total), package, i, total, addn=False)
    try:
        json = PyPIJson(package).retrieve()
        new_version = Version(json['info']['version'])
    except Exception as e:
        logger.error('Error: Failed to fetch data for {0} ({1})', package, e)
        return
    if new_version > version:
        res.put_nowait((package, version, new_version, json))

class Updater(FileManager):

    # __init__ and other methods...

    def update(self):    
        logger.info('Searching for updates')
        packages = Queue.Queue()
        data = ((packages, self.set_len, dist.project_name, Version(dist.version)) \
            for dist in self.working_set)
        pool = multiprocessing.Pool()
        pool.map(check_one, data)
        pool.close()
        pool.join()
        while True:
            try:
                package, version, new_version, json = packages.get_nowait()
            except Queue.Empty:
                break
            txt = 'A new release is avaiable for {0}: {1!s} (old {2}), update'.format(package,
                                                                                      new_version,
                                                                                      version)
            u = logger.ask(txt, bool=('upgrade version', 'keep working version'), dont_ask=self.yes)
            if u:
                self.upgrade(package, json, new_version)
            else:
                logger.info('{0} has not been upgraded', package)
        self._clean()
        logger.success('Updating finished successfully')
_

実行すると、次の奇妙なエラーが発生します。

_Searching for updates
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/local/lib/python2.7/dist-packages/multiprocessing/pool.py", line 225, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'thread.lock'>: attribute lookup thread.lock failed
_
31
rubik

マルチプロセッシングは、タスク(check_oneおよびdataを含む)をmp.SimpleQueueを介してワーカープロセスに渡します。 Queue.Queuesとは異なり、mp.SimpleQueueに入れるものはすべて選択可能でなければなりません。 Queue.Queuesは選択できません:

import multiprocessing as mp
import Queue

def foo(queue):
    pass

pool=mp.Pool()
q=Queue.Queue()

pool.map(foo,(q,))

この例外が発生します:

UnpickleableError: Cannot pickle <type 'thread.lock'> objects

dataには、Queue.Queueであるpackagesが含まれます。それが問題の原因かもしれません。


考えられる回避策は次のとおりです。Queueは2つの目的で使用されています。

  1. おおよそのサイズを見つけるには(qsizeを呼び出して)
  2. 後で取得できるように結果を保存します。

qsizeを呼び出す代わりに、複数のプロセス間で値を共有するために、mp.Valueを使用できます。

結果をキューに保存する代わりに、check_oneへの呼び出しから値を返すことができます(また、そうすべきです)。 pool.mapは結果を独自のキューに収集し、pool.mapの戻り値として結果を返します。

例えば:

import multiprocessing as mp
import Queue
import random
import logging

# logger=mp.log_to_stderr(logging.DEBUG)
logger = logging.getLogger(__name__)


qsize = mp.Value('i', 1)
def check_one(args):
    total, package, version = args
    i = qsize.value
    logger.info('\r[{0:.1%} - {1}, {2} / {3}]'.format(
        i / float(total), package, i, total))
    new_version = random.randrange(0,100)
    qsize.value += 1
    if new_version > version:
        return (package, version, new_version, None)
    else:
        return None

def update():    
    logger.info('Searching for updates')
    set_len=10
    data = ( (set_len, 'project-{0}'.format(i), random.randrange(0,100))
             for i in range(set_len) )
    pool = mp.Pool()
    results = pool.map(check_one, data)
    pool.close()
    pool.join()
    for result in results:
        if result is None: continue
        package, version, new_version, json = result
        txt = 'A new release is avaiable for {0}: {1!s} (old {2}), update'.format(
            package, new_version, version)
        logger.info(txt)
    logger.info('Updating finished successfully')

if __name__=='__main__':
    logging.basicConfig(level=logging.DEBUG)
    update()
28
unutbu

同様の問題を掘り下げた後...

また、threading.Condition()オブジェクトを含むオブジェクトがmultiprocessing.Poolで動作することは決してありません。

ここに例があります

import multiprocessing as mp
import threading

class MyClass(object):
   def __init__(self):
      self.cond = threading.Condition()

def foo(mc):
   pass

pool=mp.Pool()
mc=MyClass()
pool.map(foo,(mc,))

私はこれをPython 2.7.5で実行し、同じエラーをヒットしました:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 811, in __bootstrap_inner
self.run()
  File "/usr/lib64/python2.7/threading.py", line 764, in run
self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib64/python2.7/multiprocessing/pool.py", line 342, in _handle_tasks
put(task)
PicklingError: Can't pickle <type 'thread.lock'>: attribute lookup thread.lock failed

しかし、それをpython 3.4.1で実行し、この問題は修正されました。

まだ2.7.xを使用している私たちにとって有用な回避策はまだありませんが。

7
jersey bean

この問題は、pythonバージョン3.6でdockerで発生しました。バージョンを3.7.3に変更し、解決しました。