Existe-t-il un moyen d'affecter à chaque travailleur d'un pool de multiprocesseurs python un ID unique de manière à ce qu'un travail exécuté par un travailleur particulier du pool sache quel travailleur l'exécute? la documentation, un Process
a un name
mais
Le nom est une chaîne utilisée à des fins d'identification uniquement. Il n'a pas de sémantique. Plusieurs processus peuvent porter le même nom.
Pour mon cas d'utilisation particulier, je souhaite exécuter un tas de travaux sur un groupe de quatre GPU, et je dois définir le numéro de périphérique du GPU sur lequel le travail doit s'exécuter. Parce que les travaux sont de longueur non uniforme, je veux être sûr que je n'ai pas de collision sur un GPU d'un travail essayant de s'exécuter dessus avant que le précédent ne se termine (donc cela empêche de pré-attribuer un ID au unité de travail à l'avance).
Il semble que ce que vous voulez soit simple: multiprocessing.current_process()
. Par exemple:
import multiprocessing
def f(x):
print multiprocessing.current_process()
return x * x
p = multiprocessing.Pool()
print p.map(f, range(6))
Production:
$ python foo.py
<Process(PoolWorker-1, started daemon)>
<Process(PoolWorker-2, started daemon)>
<Process(PoolWorker-3, started daemon)>
<Process(PoolWorker-1, started daemon)>
<Process(PoolWorker-2, started daemon)>
<Process(PoolWorker-4, started daemon)>
[0, 1, 4, 9, 16, 25]
Cela renvoie l'objet de processus lui-même, afin que le processus puisse être sa propre identité. Vous pouvez également appeler id
dessus pour un identifiant numérique unique - en cpython, c'est l'adresse mémoire de l'objet de processus, donc je ne pense pas il y a une possibilité de chevauchement. Enfin, vous pouvez utiliser la propriété ident
ou pid
du processus - mais cela n'est défini qu'une fois le processus démarré.
De plus, en regardant par-dessus la source, il me semble très probable que les noms générés automatiquement (comme illustré par la première valeur dans les chaînes de repr Process
ci-dessus) sont uniques. multiprocessing
maintient un objet itertools.counter
pour chaque processus, qui est utilisé pour générer un _identity
Tuple pour tous les processus enfants qu'il génère. Ainsi, le processus de niveau supérieur produit un processus enfant avec des ID à valeur unique, et ils génèrent un processus avec des ID à deux valeurs, etc. Ensuite, si aucun nom n'est transmis au constructeur Process
, il suffit génère automatiquement le nom en fonction de l'identité, en utilisant ':'.join(...)
. Puis Pool
modifie le nom du processus en utilisant replace
, laissant l'id généré automatiquement.
Le résultat de tout cela est que même si deux Process
es peuvent avoir le même nom, car vous peut leur attribuer le même nom lorsque vous les créez, ils sont uniques si vous ne touchez pas le paramètre de nom. De plus, vous pourriez théoriquement utiliser _identity
Comme identifiant unique; mais je suppose qu'ils ont rendu cette variable privée pour une raison!
Un exemple de ce qui précède en action:
import multiprocessing
def f(x):
created = multiprocessing.Process()
current = multiprocessing.current_process()
print 'running:', current.name, current._identity
print 'created:', created.name, created._identity
return x * x
p = multiprocessing.Pool()
print p.map(f, range(6))
Production:
$ python foo.py
running: PoolWorker-1 (1,)
created: Process-1:1 (1, 1)
running: PoolWorker-2 (2,)
created: Process-2:1 (2, 1)
running: PoolWorker-3 (3,)
created: Process-3:1 (3, 1)
running: PoolWorker-1 (1,)
created: Process-1:2 (1, 2)
running: PoolWorker-2 (2,)
created: Process-2:2 (2, 2)
running: PoolWorker-4 (4,)
created: Process-4:1 (4, 1)
[0, 1, 4, 9, 16, 25]
Vous pouvez utiliser multiprocessing.Queue
Pour stocker les ID, puis obtenir l'ID lors de l'initialisation du processus de pool.
Avantages:
queue.get()
et n'effectueront aucun travail (cela ne bloquera pas votre porgram, ou du moins, ce n'était pas le cas lorsque j'ai testé).Désavantages:
sleep(1)
dans l'exemple, tout le travail pourrait être effectué par le premier processus, car d'autres n'ont pas encore terminé l'initialisation.Exemple:
import multiprocessing
from time import sleep
def init(queue):
global idx
idx = queue.get()
def f(x):
global idx
process = multiprocessing.current_process()
sleep(1)
return (idx, process.pid, x * x)
ids = [0, 1, 2, 3]
manager = multiprocessing.Manager()
idQueue = manager.Queue()
for i in ids:
idQueue.put(i)
p = multiprocessing.Pool(8, init, (idQueue,))
print(p.map(f, range(8)))
Production:
[(0, 8289, 0), (1, 8290, 1), (2, 8294, 4), (3, 8291, 9), (0, 8289, 16), (1, 8290, 25), (2, 8294, 36), (3, 8291, 49)]
Notez qu'il n'y a que 4 pid différents, bien que le pool contienne 8 processus et un idx n'est utilisé que par un seul processus.
J'ai fait cela avec le filetage et j'ai fini par utiliser ne file d'attente pour gérer la gestion des travaux. Voici la référence. Ma version complète a un tas de try-catches
(En particulier dans le travailleur, pour s'assurer que q.task_done()
est appelé même en cas d'échec).
from threading import Thread
from queue import Queue
import time
import random
def run(idx, *args):
time.sleep(random.random() * 1)
print idx, ':', args
def run_jobs(jobs, workers=1):
q = Queue()
def worker(idx):
while True:
args = q.get()
run(idx, *args)
q.task_done()
for job in jobs:
q.put(job)
for i in range(0, workers):
t = Thread(target=worker, args=[i])
t.daemon = True
t.start()
q.join()
if __== "__main__":
run_jobs([('job', i) for i in range(0,10)], workers=5)
Je n'avais pas besoin d'utiliser le multitraitement (mes employés sont juste pour appeler un processus externe), mais cela pourrait être étendu. L'API pour le multitraitement le modifie, voici comment vous pouvez vous adapter:
from multiprocessing import Process, Queue
from Queue import Empty
import time
import random
def run(idx, *args):
time.sleep(random.random() * i)
print idx, ':', args
def run_jobs(jobs, workers=1):
q = Queue()
def worker(idx):
try:
while True:
args = q.get(timeout=1)
run(idx, *args)
except Empty:
return
for job in jobs:
q.put(job)
processes = []
for i in range(0, workers):
p = Process(target=worker, args=[i])
p.daemon = True
p.start()
processes.append(p)
for p in processes:
p.join()
if __== "__main__":
run_jobs([('job', i) for i in range(0,10)], workers=5)
Les deux versions produiront quelque chose comme:
0 : ('job', 0)
1 : ('job', 2)
1 : ('job', 6)
3 : ('job', 3)
0 : ('job', 5)
1 : ('job', 7)
2 : ('job', 1)
4 : ('job', 4)
3 : ('job', 8)
0 : ('job', 9)