multiprocessing.Pool
Me rend fou ...
Je souhaite mettre à niveau de nombreux packages, et pour chacun d'entre eux, je dois vérifier s'il existe une version supérieure ou non. Cela se fait par la fonction check_one
.
Le code principal est dans la méthode Updater.update
: Là je crée l'objet Pool et j'appelle la méthode map()
.
Voici le code:
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')
Quand je l'exécute, j'obtiens cette erreur étrange:
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
le multitraitement passe des tâches (qui incluent check_one
et data
) aux processus de travail via un mp.SimpleQueue
. Contrairement à Queue.Queue
s, tout est mis dans le mp.SimpleQueue
doit être sélectionnable. Queue.Queue
s ne sont pas sélectionnables:
import multiprocessing as mp
import Queue
def foo(queue):
pass
pool=mp.Pool()
q=Queue.Queue()
pool.map(foo,(q,))
donne cette exception:
UnpickleableError: Cannot pickle <type 'thread.lock'> objects
Votre data
comprend packages
, qui est une file d'attente. Cela pourrait être la source du problème.
Voici une solution de contournement possible: Queue
est utilisé à deux fins:
qsize
)Au lieu d'appeler qsize
, pour partager une valeur entre plusieurs processus, nous pourrions utiliser un mp.Value
.
Au lieu de stocker les résultats dans une file d'attente, nous pouvons (et devons) simplement renvoyer les valeurs des appels à check_one
. Le pool.map
recueille les résultats dans une file d'attente de sa propre création et renvoie les résultats en tant que valeur de retour de pool.map
.
Par exemple:
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()
Après avoir beaucoup creusé sur un problème similaire ...
Il s'avère également que TOUT objet contenant un objet threading.Condition () ne fonctionnera JAMAIS avec le multiprocessing.Pool.
Voici un exemple
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,))
Je l'ai exécuté avec Python 2.7.5 et j'ai rencontré la même erreur:
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
Mais ensuite, je l'ai exécuté sur python 3.4.1 et ce problème a été corrigé.
Bien que je n'aie pas encore trouvé de solutions de contournement utiles pour ceux d'entre nous qui sont encore sur 2.7.x.
J'ai rencontré ce problème avec python version 3.6 sur docker. Changé la version en 3.7.3 et il a été résolu.