Como fazer com que todos os processos pool.apply_async parem quando um processo encontra uma correspondência em python
Eu tenho o código a seguir que está aproveitando o multiprocessamento para percorrer uma lista grande e encontrar uma correspondência. Como posso interromper todos os processos depois que uma correspondência é encontrada em qualquer um dos processos? Já vi exemplos, mas nenhum deles parece se encaixar no que estou fazendo aqui.
#!/usr/bin/env python3.5
import sys, itertools, multiprocessing, functools
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12234567890!@#$%^&*?,()-=+[]/;"
num_parts = 4
part_size = len(alphabet) // num_parts
def do_job(first_bits):
for x in itertools.product(first_bits, *itertools.repeat(alphabet, num_parts-1)):
# CHECK FOR MATCH HERE
print(''.join(x))
# EXIT ALL PROCESSES IF MATCH FOUND
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=4)
results = []
for i in range(num_parts):
if i == num_parts - 1:
first_bit = alphabet[part_size * i :]
else:
first_bit = alphabet[part_size * i : part_size * (i+1)]
pool.apply_async(do_job, (first_bit,))
pool.close()
pool.join()
Obrigado pelo seu tempo.
ATUALIZAÇÃO 1:
Eu implementei as mudanças sugeridas na ótima abordagem do @ShadowRanger e ele está quase funcionando da maneira que eu quero. Então, adicionei alguns registros para dar uma indicação do progresso e colocar uma chave 'test' lá para combinar. Eu quero poder aumentar / diminuir o iNumberOfProcessors independentemente do num_parts. Nesta fase, quando eu tenho os dois no 4, tudo funciona como esperado, 4 processos são ativados (um extra para o console). Quando altero o processo iNumberOfProcessors = 6, 6, os processos são ativados, mas apenas um deles possui uso de CPU. Portanto, parece que 2 estão ociosos. Onde, como minha solução anterior acima, eu era capaz de definir o número de núcleos mais alto sem aumentar o num_parts, e todos os processos seriam usados.
Não tenho certeza de como refatorar essa nova abordagem para me fornecer a mesma funcionalidade. Você pode dar uma olhada e me dar alguma orientação com a refatoração necessária para poder definir iNumberOfProcessors e num_parts independentemente um do outro e ainda ter todos os processos usados?
Aqui está o código atualizado:
#!/usr/bin/env python3.5
import sys, itertools, multiprocessing, functools
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12234567890!@#$%^&*?,()-=+[]/;"
num_parts = 4
part_size = len(alphabet) // num_parts
iProgressInterval = 10000
iNumberOfProcessors = 6
def do_job(first_bits):
iAttemptNumber = 0
iLastProgressUpdate = 0
for x in itertools.product(first_bits, *itertools.repeat(alphabet, num_parts-1)):
sKey = ''.join(x)
iAttemptNumber = iAttemptNumber + 1
if iLastProgressUpdate + iProgressInterval <= iAttemptNumber:
iLastProgressUpdate = iLastProgressUpdate + iProgressInterval
print("Attempt#:", iAttemptNumber, "Key:", sKey)
if sKey == 'test':
print("KEY FOUND!! Attempt#:", iAttemptNumber, "Key:", sKey)
return True
def get_part(i):
if i == num_parts - 1:
first_bit = alphabet[part_size * i :]
else:
first_bit = alphabet[part_size * i : part_size * (i+1)]
return first_bit
if __name__ == '__main__':
# with statement with Py3 multiprocessing.Pool terminates when block exits
with multiprocessing.Pool(processes = iNumberOfProcessors) as pool:
# Don't need special case for final block; slices can
for gotmatch in pool.imap_unordered(do_job, map(get_part, range(num_parts))):
if gotmatch:
break
else:
print("No matches found")
ATUALIZAÇÃO 2:
Ok, aqui está minha tentativa de tentar a sugestão do @noxdafox. Reuni o seguinte com base no link que ele forneceu com sua sugestão. Infelizmente, quando o executo, recebo o erro:
... linha 322, em apply_async, eleva ValueError ("Pool não está executando") ValueError: pool não está executando
Alguém pode me dar alguma orientação sobre como fazer isso funcionar.
Basicamente, o problema é que minha primeira tentativa fez o multiprocessamento, mas não deu suporte ao cancelamento de todos os processos depois que uma correspondência foi encontrada.
Minha segunda tentativa (com base na sugestão @ShadowRanger) resolveu esse problema, mas quebrou a funcionalidade de poder escalar o número de processos e o tamanho de num_parts independentemente, o que é algo que minha primeira tentativa poderia fazer.
Minha terceira tentativa (com base na sugestão @noxdafox) lança o erro descrito acima.
Se alguém puder me dar alguma orientação sobre como manter a funcionalidade da minha primeira tentativa (poder escalar o número de processos e o tamanho de num_parts independentemente) e adicionar a funcionalidade de cancelar todos os processos assim que uma correspondência for encontrada, seria muito apreciado .
Obrigado pelo seu tempo.
Aqui está o código da minha terceira tentativa, com base na sugestão @noxdafox:
#!/usr/bin/env python3.5
import sys, itertools, multiprocessing, functools
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12234567890!@#$%^&*?,()-=+[]/;"
num_parts = 4
part_size = len(alphabet) // num_parts
iProgressInterval = 10000
iNumberOfProcessors = 4
def find_match(first_bits):
iAttemptNumber = 0
iLastProgressUpdate = 0
for x in itertools.product(first_bits, *itertools.repeat(alphabet, num_parts-1)):
sKey = ''.join(x)
iAttemptNumber = iAttemptNumber + 1
if iLastProgressUpdate + iProgressInterval <= iAttemptNumber:
iLastProgressUpdate = iLastProgressUpdate + iProgressInterval
print("Attempt#:", iAttemptNumber, "Key:", sKey)
if sKey == 'test':
print("KEY FOUND!! Attempt#:", iAttemptNumber, "Key:", sKey)
return True
def get_part(i):
if i == num_parts - 1:
first_bit = alphabet[part_size * i :]
else:
first_bit = alphabet[part_size * i : part_size * (i+1)]
return first_bit
def grouper(iterable, n, fillvalue=None):
args = [iter(iterable)] * n
return itertools.zip_longest(*args, fillvalue=fillvalue)
class Worker():
def __init__(self, workers):
self.workers = workers
def callback(self, result):
if result:
self.pool.terminate()
def do_job(self):
print(self.workers)
pool = multiprocessing.Pool(processes=self.workers)
for part in grouper(alphabet, part_size):
pool.apply_async(do_job, (part,), callback=self.callback)
pool.close()
pool.join()
print("All Jobs Queued")
if __name__ == '__main__':
w = Worker(4)
w.do_job()