web-dev-qa-db-fra.com

Obtenez un ID unique pour le travailleur dans python pool multiprocessing

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).

36
JoshAdel

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 Poolmodifie 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 Processes 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]
62
senderle

Vous pouvez utiliser multiprocessing.Queue Pour stocker les ID, puis obtenir l'ID lors de l'initialisation du processus de pool.

Avantages:

  • Vous n'avez pas besoin de vous fier aux internes.
  • Si votre cas d'utilisation est de gérer des ressources/appareils, vous pouvez saisir directement le numéro de l'appareil. Cela garantira également qu'aucun périphérique n'est utilisé deux fois: si vous avez plus de processus dans votre pool que de périphériques, les processus supplémentaires bloqueront sur 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:

  • Vous avez des frais de communication supplémentaires et la génération des processus de pool prend un tout petit peu plus longtemps: sans 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.
  • Vous avez besoin d'un global (ou du moins je ne sais pas comment le contourner)

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.

3
Steohan

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)
1
RyanD