Да предположим, че имам N генератора gen_1, ..., gen_N
, където всеки от тях ще даде същия брой стойности. Бих искал генератор gen
такъв, че да изпълнява gen_1, ..., gen_N в N паралелни процеса и да дава (next(gen_1), next(gen_2), ... next(gen_N))
Тоест бих искал да имам:
def gen():
yield (next(gen_1), next(gen_2), ... next(gen_N))
по такъв начин, че всеки gen_i да работи на свой собствен процес. Възможно ли е да се направи това? Опитах се да направя това в следния фиктивен пример без успех:
A = range(4)
def gen(a):
B = ['a', 'b', 'c']
for b in B:
yield b + str(a)
def target(g):
return next(g)
processes = [Process(target=target, args=(gen(a),)) for a in A]
for p in processes:
p.start()
for p in processes:
p.join()
Въпреки това получавам грешката TypeError: cannot pickle 'generator' object
.
РЕДАКТИРАНЕ:
Промених малко отговора на @darkonaut, за да отговаря на нуждите ми. Публикувам го, ако някой от вас го намери за полезен. Първо дефинираме няколко полезни функции:
from itertools import zip_longest
from typing import List, Generator
def grouper(iterable, n, fillvalue=iter([])):
"Collect data into fixed-length chunks or blocks"
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
def split_generators_into_batches(generators: List[Generator], n_splits):
chunks = grouper(generators, len(generators) // n_splits + 1)
return [zip_longest(*chunk) for chunk in chunks]
Следният клас е отговорен за разделянето на произволен брой генератори на n (брой процеси) партиди и обработването им, което води до желания резултат:
import multiprocessing as mp
class GeneratorParallelProcessor:
SENTINEL = 'S'
def __init__(self, generators, n_processes = 2 * mp.cpu_count()):
self.n_processes = n_processes
self.generators = split_generators_into_batches(list(generators), n_processes)
self.queue = mp.SimpleQueue()
self.barrier = mp.Barrier(n_processes + 1)
self.sentinels = [self.SENTINEL] * n_processes
self.processes = [
mp.Process(target=self._worker, args=(self.barrier, self.queue, gen)) for gen in self.generators
]
def process(self):
for p in self.processes:
p.start()
while True:
results = list(itertools.chain(*(self.queue.get() for _ in self.generators)))
if results != self.sentinels:
yield results
self.barrier.wait()
else:
break
for p in self.processes:
p.join()
def _worker(self, barrier, queue, generator):
for x in generator:
queue.put(x)
barrier.wait()
queue.put(self.SENTINEL)
За да го използвате, просто направете следното:
parallel_processor = GeneratorParallelProcessor(generators)
for grouped_generator in parallel_processor.process():
output_handler(grouped_generator)
Process
с целева функция, която ще създаде генератора там. - person jasonharper   schedule 10.10.2020multiprocessing
, а не нишки, така че не мисля, че GIL се прилага тук. - person thegamecracks   schedule 10.10.2020