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?
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
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]
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.
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,)
).
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 Queue
dans main()
et de l'exécuter sur un Thread
distinct avant le démarrage du pool. Assurez-vous ensuite d'envoyer l'objet Queue
dans 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.
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
}
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).
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()