큰 팬더 데이터 프레임을 전달하기 위해 대기열을 사용하여 다중 처리를 수행하는 방법을 설정하는 동안 스택 오버플로와 웹에서 여러 답변을 살펴 보았습니다. 모든 답변이 이와 같은 계산을 설정할 때 분명히 접하게 될 수많은 엣지 케이스를 고려하지 않고 동일한 종류의 솔루션을 반복하는 것처럼 보였습니다. 문제는 동시에 많은 것이 있다는 것입니다. 작업 수, 작업자 수, 각 작업 기간 및 작업 실행 중 가능한 예외. 이 모든 것들은 동기화를 어렵게 만들고 대부분의 답변은 어떻게 할 수 있는지를 다루지 않습니다. 그래서 이것은 몇 시간 동안 어슬렁 거리고 난 후에 제가 취한 것입니다. 바라건대 대부분의 사람들이 유용하다고 생각하기에 충분히 일반적 일 것입니다.
코딩 예제 전에 몇 가지 생각. queue.Empty
또는 queue.qsize()
기타 유사한 방법은 흐름 제어에 대해 신뢰할 수 없기 때문에 유사한 코드
while True:
try:
task = pending_queue.get_nowait()
except queue.Empty:
break
가짜입니다. 밀리 초 후에 다른 작업이 대기열에 표시 되더라도 작업자가 죽습니다. 작업자는 회복되지 않으며 잠시 후 모든 작업자는 무작위로 대기열이 일시적으로 비어 있음을 발견하므로 사라집니다. 최종 결과는 모든 작업이 완료되지 않은 상태에서 주요 다중 처리 함수 (프로세스에 join ()가있는 함수)가 반환됩니다. 좋은. 수천 개의 작업이 있고 일부가 누락 된 경우이를 통해 디버깅을 행운을 빌어 요.
다른 문제는 센티넬 값의 사용입니다. 많은 사람들이 대기열의 끝을 표시하기 위해 대기열에 센티넬 값을 추가 할 것을 제안했습니다. 그러나 정확히 누구에게 표시하려면? N 개의 작업자가있는 경우 N이 주거나받을 수있는 코어 수라고 가정하면 단일 센티넬 값은 한 작업자에게만 대기열의 끝을 표시합니다. 다른 모든 작업자는 남은 작업이 없을 때 더 많은 작업을 기다릴 것입니다. 내가 본 전형적인 예는
while True:
task = pending_queue.get()
if task == SOME_SENTINEL_VALUE:
break
한 작업자는 센티널 값을 받고 나머지는 무기한 대기합니다. 내가 만난 게시물은 작업자가있는만큼 최소한 큐에 센티넬 값을 제출해야 모든 작업자가 얻을 수 있다고 언급 한 게시물이 없습니다.
다른 문제는 작업 실행 중 예외 처리입니다. 다시 이것들을 잡아서 관리해야합니다. 또한 completed_tasks
대기열 이있는 경우 작업이 완료되기 전에 대기열에있는 항목 수를 결정적인 방식으로 독립적으로 계산해야합니다. 다시 대기열 크기에 의존하면 실패하고 예기치 않은 결과가 반환됩니다.
아래 예에서 par_proc()
함수는 명명 된 인수 및 값과 함께 이러한 작업을 실행해야하는 함수를 포함한 작업 목록을받습니다.
import multiprocessing as mp
import dill as pickle
import queue
import time
import psutil
SENTINEL = None
def do_work(tasks_pending, tasks_completed):
worker_name = mp.current_process().name
while True:
try:
task = tasks_pending.get_nowait()
except queue.Empty:
print(worker_name + ' found an empty queue. Sleeping for a while before checking again...')
time.sleep(0.01)
else:
try:
if task == SENTINEL:
print(worker_name + ' no more work left to be done. Exiting...')
break
print(worker_name + ' received some work... ')
time_start = time.perf_counter()
work_func = pickle.loads(task['func'])
result = work_func(**task['task'])
tasks_completed.put({work_func.__name__: result})
time_end = time.perf_counter() - time_start
print(worker_name + ' done in {} seconds'.format(round(time_end, 5)))
except Exception as e:
print(worker_name + ' task failed. ' + str(e))
tasks_completed.put({work_func.__name__: None})
def par_proc(job_list, num_cpus=None):
if not num_cpus:
num_cpus = psutil.cpu_count(logical=False)
print('* Parallel processing')
print('* Running on {} cores'.format(num_cpus))
tasks_pending = mp.Queue()
tasks_completed = mp.Queue()
processes = []
results = []
num_tasks = 0
for job in job_list:
for task in job['tasks']:
expanded_job = {}
num_tasks = num_tasks + 1
expanded_job.update({'func': pickle.dumps(job['func'])})
expanded_job.update({'task': task})
tasks_pending.put(expanded_job)
num_workers = num_cpus
for c in range(num_workers):
tasks_pending.put(SENTINEL)
print('* Number of tasks: {}'.format(num_tasks))
for c in range(num_workers):
p = mp.Process(target=do_work, args=(tasks_pending, tasks_completed))
p.name = 'worker' + str(c)
processes.append(p)
p.start()
completed_tasks_counter = 0
while completed_tasks_counter < num_tasks:
results.append(tasks_completed.get())
completed_tasks_counter = completed_tasks_counter + 1
for p in processes:
p.join()
return results
그리고 여기에 위의 코드를 실행하는 테스트가 있습니다.
def test_parallel_processing():
def heavy_duty1(arg1, arg2, arg3):
return arg1 + arg2 + arg3
def heavy_duty2(arg1, arg2, arg3):
return arg1 * arg2 * arg3
task_list = [
{'func': heavy_duty1, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
{'func': heavy_duty2, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
]
results = par_proc(task_list)
job1 = sum([y for x in results if 'heavy_duty1' in x.keys() for y in list(x.values())])
job2 = sum([y for x in results if 'heavy_duty2' in x.keys() for y in list(x.values())])
assert job1 == 15
assert job2 == 21
몇 가지 예외가있는 다른 하나
def test_parallel_processing_exceptions():
def heavy_duty1_raises(arg1, arg2, arg3):
raise ValueError('Exception raised')
return arg1 + arg2 + arg3
def heavy_duty2(arg1, arg2, arg3):
return arg1 * arg2 * arg3
task_list = [
{'func': heavy_duty1_raises, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
{'func': heavy_duty2, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
]
results = par_proc(task_list)
job1 = sum([y for x in results if 'heavy_duty1' in x.keys() for y in list(x.values())])
job2 = sum([y for x in results if 'heavy_duty2' in x.keys() for y in list(x.values())])
assert not job1
assert job2 == 21
도움이 되길 바랍니다.