web-dev-qa-db-fra.com

Obtention de l'index de l'entrée en cours d'exécution dans le multitraitement python

    from multiprocessing import Pool
    with Pool(processes=5) as p:
        p.starmap(name_of_function, all_inputs)

J'ai un morceau de code comme ci-dessus qui exécute une fonction en parallèle. En supposant que all_inputs compte 10 000 éléments, j'aimerais savoir lequel exécute actuellement, par exemple. 100 sur 10 000 ... Y a-t-il un moyen d'obtenir cet indice?

14
user308827

Le processus de travail dans multiprocessing.Pool est une instance de Process, il conserve un compteur interne pour s'identifier, vous pouvez utiliser ce compteur avec l'ID de processus du système d'exploitation :

import os
from multiprocessing import current_process, Pool


def x(a):
    p = current_process()
    print('process counter:', p._identity[0], 'pid:', os.getpid())


if __== '__main__':
    with Pool(2) as p:
        r = p.map(x, range(4))
    p.join()

rendements:

process counter: 1 pid: 29443
process counter: 2 pid: 29444
process counter: 2 pid: 29444
process counter: 1 pid: 29443
9
georgexsh

Vous pouvez utiliser la méthode current_process à partir du multitraitement. Si cela n’est pas assez précis, vous pouvez même transmettre aux processus une name en utilisant une uuid

from multiprocessing import current_process


def x(a):
    print(current_process(), a)
    return a*a

with Pool(5) as p:
    p.map(x, [1,2,3,4,5]
5
user1767754

IIUC, vous pouvez également transmettre les index. (Volez la configuration de @ user1767754) (Merci de me faire savoir si ce n'est pas ce que vous recherchez.)

from multiprocessing import Pool

arr = [1,2,3,4,5]
arr_with_idx = Zip(arr, range(len(arr)))

def x(a, idx):
    print(idx)
    return a*a

with Pool(5) as p:
    p.starmap(x, arr_with_idx)

Ou plus simplement, utilisez enumerate

from multiprocessing import Pool

arr = [1,2,3,4,5]

def x(idx, a):  # different here
    print(idx)
    return a*a

with Pool(5) as p:
    p.starmap(x, enumerate(arr))

starmap va déballer chaque tuple et vous pouvez imprimer la partie index.

3
Tai

Je suggère de passer l'index avec les autres arguments. Vous pouvez utiliser enumerate éventuellement associé à une expression de générateur pour ajouter la valeur à vos arguments existants. Voici le code qui suppose que all_inputs est un itérable de tuples:

with Pool(processes=5) as p:
    p.starmap(name_of_function, ((i,) + args for i, args in enumerate(all_inputs)))

Vous pouvez choisir parmi de nombreuses variations sur ce thème général. Par exemple, vous pouvez placer l'index à la fin des arguments plutôt qu'au début (il suffit de permuter (i,) + args à args + (i,)).

2
Blckknght

Le moyen le plus simple de découvrir quel processus traite la valeur 100 consiste à diviser le tableau en parties égales avant le démarrage du pool . Cela vous permet de contrôler les processus du pool qui gèrent quelle partie du tableau (puisque vous transmettez des valeurs prédéfinies start, end index pour que chacun d’eux fonctionne).

Par exemple, si all_inputs avait 50 éléments avec des valeurs allant de 80 à 130, une simple division de travail pour un CPU à 8 cœurs renverrait les paires d'index suivants:

Process #0 will work on indexes [0:5] with values: 80 - 85
Process #1 will work on indexes [6:11] with values: 86 - 91
Process #2 will work on indexes [12:17] with values: 92 - 97
Process #3 will work on indexes [18:23] with values: 98 - 103
Process #4 will work on indexes [24:29] with values: 104 - 109
Process #5 will work on indexes [30:35] with values: 110 - 115
Process #6 will work on indexes [36:41] with values: 116 - 121
Process #7 will work on indexes [42:50] with values: 122 - 130

Lorsque le pool démarre, vous savez déjà lequel sera responsable du traitement de la valeur 100.

Le succès de cette approche repose sur jobDiv() pour effectuer sa magie et diviser les données des processus, en fonction de la taille du tableau en entrée et du nombre de cœurs de processeur disponibles.

Code source :

import multiprocessing as mp    

# processArray(): a parallel function that process an array based on start and 
#   end index positions.
def processArray(procId, array, indexes):
    startIdx, endIdx = indexes
    print("  Process #" + str(procId) + " startIdx=" + str(startIdx), " endIdx=" + str(endIdx))

    # Do some work:
    for i in range(startIdx, endIdx+1):
        print("    Process #" + str(procId) + " is computing index " + str(i), " with value " + str(array[i]))


# jobDiv(): performs a simple job division between available CPU cores
def jobDiv(inputArray, numCPUs):
    jobs = []
    arrayLength = len(inputArray)

    jobRange = int(arrayLength / numCPUs)
    extra = arrayLength - (jobRange * numCPUs)

    prevEnd = 0
    for c in range(numCPUs):
        endIdx = (c * jobRange) + jobRange - 1
        if (c == (numCPUs-1)):
            endIdx += extra

        startIdx = prevEnd
        if ( (c > 0) and (startIdx+1 < arrayLength) ):
            startIdx += 1

        jobs.append( (startIdx, endIdx) )
        prevEnd = endIdx

    return jobs


if __== '__main__':
    # Initialize dataset for multiprocessing with 50 numbers, with values from 80 to 131
    nums = range(80, 131)

    # How many CPU cores can be used for this dataset
    numCPUs = mp.cpu_count()
    if (numCPUs > len(nums)):
        numCPUs = len(nums)

    # This function returns a list of tuples containing array indexes for
    # each process to work on. When nums has 100 elements and numCPUs is 8,
    # it returns the following list:
    #   (0, 11), (12, 23), (24, 35), (36, 47), (48, 59), (60, 71), (72, 83), (84, 99)
    indexes = jobDiv(nums, numCPUs)

    # Prepare parameters for every process in the pool, where each process gets one Tuple of:
    #   (cpu_id, array, array_indexes)
    jobArgs = []
    for id, arg in enumerate(indexes):
        start, end = arg
        print("Process #" + str(id) + " will work on indexes [" + str(start) + ":" + str(end) +
              "] with values: " + str(nums[start]) + " - " + str(nums[end]))
        jobArgs.append( (id, nums, arg) )

    print("* Starting Pool")

    # For every process, send the data for processing along with it's respective Tuple of parameters
    with mp.Pool(processes=numCPUs) as p:
        sums = p.starmap(processArray, jobArgs)

    print("* Finished")

Sortie :

* Starting Pool
  Process #0 startIdx=0  endIdx=5
    Process #0 is computing index 0  with value 80
    Process #0 is computing index 1  with value 81
    Process #0 is computing index 2  with value 82
    Process #0 is computing index 3  with value 83
    Process #0 is computing index 4  with value 84
    Process #0 is computing index 5  with value 85
  Process #1 startIdx=6  endIdx=11
    Process #1 is computing index 6  with value 86
    Process #1 is computing index 7  with value 87
    Process #1 is computing index 8  with value 88
    Process #1 is computing index 9  with value 89
    Process #1 is computing index 10  with value 90
    Process #1 is computing index 11  with value 91
  Process #2 startIdx=12  endIdx=17
    Process #2 is computing index 12  with value 92
    Process #2 is computing index 13  with value 93
    Process #2 is computing index 14  with value 94
    Process #2 is computing index 15  with value 95
    Process #2 is computing index 16  with value 96
    Process #2 is computing index 17  with value 97
  Process #3 startIdx=18  endIdx=23
    Process #3 is computing index 18  with value 98
    Process #3 is computing index 19  with value 99
    Process #3 is computing index 20  with value 100
    Process #3 is computing index 21  with value 101
    Process #3 is computing index 22  with value 102
  Process #4 startIdx=24  endIdx=29
    Process #3 is computing index 23  with value 103
    Process #4 is computing index 24  with value 104
    Process #4 is computing index 25  with value 105
    Process #4 is computing index 26  with value 106
    Process #4 is computing index 27  with value 107
    Process #4 is computing index 28  with value 108
    Process #4 is computing index 29  with value 109
  Process #5 startIdx=30  endIdx=35
    Process #5 is computing index 30  with value 110
    Process #5 is computing index 31  with value 111
    Process #5 is computing index 32  with value 112
    Process #5 is computing index 33  with value 113
    Process #5 is computing index 34  with value 114
    Process #5 is computing index 35  with value 115
  Process #6 startIdx=36  endIdx=41
    Process #6 is computing index 36  with value 116
    Process #6 is computing index 37  with value 117
    Process #6 is computing index 38  with value 118
  Process #7 startIdx=42  endIdx=50
    Process #6 is computing index 39  with value 119
    Process #6 is computing index 40  with value 120
    Process #7 is computing index 42  with value 122
    Process #6 is computing index 41  with value 121
    Process #7 is computing index 43  with value 123
    Process #7 is computing index 44  with value 124
    Process #7 is computing index 45  with value 125
    Process #7 is computing index 46  with value 126
    Process #7 is computing index 47  with value 127
    Process #7 is computing index 48  with value 128
    Process #7 is computing index 49  with value 129
    Process #7 is computing index 50  with value 130
* Finished

Cela vaut la peine de souligner l'évidence et de dire que chaque processus sait quelle valeur est traitée à l'heure actuelle, cependant, main() n'en a pas

main() connaît simplement la plage d'index sur laquelle chaque processus fonctionnera, mais il ne sait pas (en temps réel) quelles sont les valeurs en cours de traitement par ces processus. 

Si vous avez besoin de main() pour accéder à ces informations pendant l'exécution des processus, il est probablement préférable de configurer un Queuedans main() et de l'exécuter sur un Threaddistinct avant le démarrage du pool. Assurez-vous ensuite d'envoyer l'objet Queuedans le cadre des paramètres transmis à tous les processus afin qu'ils puissent tous partager le même objet et stocker les données en cours de traitement.

1
karlphillip

Si vous exécutez votre code sur un processeur multicœur moderne à un moment donné, vos processus de travail exécuteront plusieurs tâches en parallèle. Vous pouvez utiliser une file d'attente pour implémenter un protocole personnalisé que vos travailleurs informeront le processus principal avec la tâche (index de la tâche) sur laquelle ils commencent et finissent leur travail.

import os
import time
import queue
import random
import multiprocessing

def fn(st_queue, i):
    st_queue.put((multiprocessing.current_process().name, i))
    time.sleep(random.random())  # your long calculation
    st_queue.put((multiprocessing.current_process().name, None))
    return i ** 2

def main():
    status = {}
    st_queue = multiprocessing.Manager().Queue()

    result = []
    pool = multiprocessing.Pool(4)
    args = Zip([st_queue] * 20, range(20))
    async_res = pool.starmap_async(
        fn, args, callback = lambda r: result.append(r))

    while not async_res.ready():
        try:
            msg = st_queue.get(True, 0.1)
        except queue.Empty:
            pass
        else:
            status.update([msg])
            print(status)      
    print(result.pop())
    pool.close()

if __== '__main__':
    main()

Le dictionnaire d'état ressemble à ceci:

{
    'ForkPoolWorker-4': None, 
    'ForkPoolWorker-5': 16, 
    'ForkPoolWorker-2': 18, 
    'ForkPoolWorker-3': 15
}
0
saaj

Si vous êtes content de savoir quand chaque élément est terminé (au lieu d'être commencé), vous pouvez utiliser apply_async avec un rappel, comme ceci:

# dummy data and function
all_inputs = list(Zip(range(10), range(20,30)))
def name_of_function(a, b):
    return a+b

# main code
from multiprocessing import Pool

num_items = len(all_inputs)
num_done = 0
def handle_result(res):
    global num_done
    num_done += 1
    print('finished item {} of {}.'.format(num_done, num_items))

p = Pool(5)
for args in all_inputs:
    p.apply_async(name_of_function, args, callback=handle_result)
p.close()
p.join() # wait for tasks to finish

résultat:

finished item 1 of 10.
finished item 2 of 10.
finished item 3 of 10.
finished item 4 of 10.
finished item 5 of 10.
finished item 6 of 10.
finished item 7 of 10.
finished item 8 of 10.
finished item 9 of 10.
finished item 10 of 10.

Notez que les résultats ne seront pas nécessairement dans le même ordre que all_inputs. Si vous avez besoin de savoir exactement quel élément a été traité, vous pouvez énumérer les arguments suivants:

from multiprocessing import Pool

num_items = len(all_inputs)
num_done = 0
def handle_result(idx):
    global num_done
    num_done += 1
    print('finished index {} ({}/{}).'.format(idx, num_done, num_items))

def wrapper(idx, args):
    name_of_function(*args)
    return idx

p = Pool(5)
for args in enumerate(all_inputs):
    p.apply_async(wrapper, args, callback=handle_result)
p.close()
p.join() # wait for tasks to finish

résultat:

finished index 0 (1/10).
finished index 1 (2/10).
finished index 2 (3/10).
finished index 3 (4/10).
finished index 4 (5/10).
finished index 6 (6/10).
finished index 8 (7/10).
finished index 7 (8/10).
finished index 9 (9/10).
finished index 5 (10/10).
0
Matthias Fripp

Si vous souhaitez signaler l'index à partir des processus de production et que cela ne vous dérange pas de le signaler en séquence, vous pouvez utiliser les méthodes de dénombrement suggérées par d'autres. Si vous souhaitez suivre le travail depuis le processus principal (par exemple, pour gérer une barre d'état) et/ou si vous avez besoin de connaître le nombre total d'éléments démarrés, vous devez demander à chaque utilisateur de faire rapport à le parent comme il commence. Cela peut être fait avec un tuyau comme indiqué ci-dessous. 

Je ne suis pas sûr que vous souhaitiez rapporter l'index de l'élément ou le nombre total d'éléments démarrés (ils peuvent commencer hors séquence), alors je rapporte les deux.

# dummy data and function
all_inputs = list(Zip(range(10), range(20,30)))
def name_of_function(a, b):
    return a+b

# main code
from multiprocessing import Pool, Pipe, Lock

parent_conn, child_conn = Pipe()
lock = Lock()

def wrapper(idx, args):
    with lock:
        child_conn.send(idx)
    return name_of_function(*args)

with Pool(processes=5) as p:
    p.starmap_async(wrapper, enumerate(all_inputs))
    # receive status updates
    num_items = len(all_inputs)
    for i in range(num_items):
        idx = parent_conn.recv()
        print("processing index {} ({}/{})".format(idx, i+1, num_items))

child_conn.close()
parent_conn.close()

# output (note that items may be started out of sequence):
# processing index 0 (1/10)
# processing index 1 (2/10)
# processing index 2 (3/10)
# processing index 3 (4/10)
# processing index 5 (5/10)
# processing index 6 (6/10)
# processing index 4 (7/10)
# processing index 7 (8/10)
# processing index 8 (9/10)
# processing index 9 (10/10)

Notez que ceci utilise starmap_async au lieu de starmap, afin de poursuivre l'exécution du thread principal pendant l'exécution des sous-processus. Vous pouvez également utiliser starmap et lancer un thread distinct pour signaler les progrès, comme suit:

from threading import Thread
from multiprocessing import Pool, Pipe, Lock
parent_conn, child_conn = Pipe()
lock = Lock()

def receive_updates(num_items):
    for i in range(num_items):
        idx = parent_conn.recv()
        print("processing index {} ({}/{})".format(idx, i+1, num_items))

def wrapper(idx, args):
    with lock:
        child_conn.send(idx)
    return name_of_function(*args)

# launch another thread to receive the results, since the main thread
# will wait for starmap
result_thread = Thread(target=receive_updates, args=(len(all_inputs),))
result_thread.Daemon = True  # stop if main thread is killed
result_thread.start()

# your original code
with Pool(processes=5) as p:
    p.starmap(wrapper, all_inputs)

child_conn.close()
parent_conn.close()
0
Matthias Fripp