Tôi đã xem xét nhiều câu trả lời trên tràn ngăn xếp và web trong khi cố gắng thiết lập một cách thực hiện đa xử lý bằng cách sử dụng hàng đợi để chuyển xung quanh các khung dữ liệu gấu trúc lớn. Đối với tôi, dường như mọi câu trả lời đều lặp lại cùng một loại giải pháp mà không cần xem xét đến vô số trường hợp cạnh mà người ta chắc chắn sẽ gặp phải khi thiết lập các phép tính như thế này. Vấn đề là có nhiều thứ diễn ra cùng một lúc. Số lượng nhiệm vụ, số lượng công nhân, thời lượng của mỗi nhiệm vụ và các ngoại lệ có thể xảy ra trong quá trình thực thi nhiệm vụ. Tất cả những điều này làm cho việc đồng bộ hóa trở nên phức tạp và hầu hết các câu trả lời không giải quyết được cách bạn có thể thực hiện nó. Vì vậy, đây là kinh nghiệm của tôi sau khi mày mò trong vài giờ, hy vọng điều này sẽ đủ chung để hầu hết mọi người thấy nó hữu ích.
Một số suy nghĩ trước bất kỳ ví dụ mã hóa nào. Vì queue.Empty
hoặc queue.qsize()
hoặc bất kỳ phương pháp tương tự nào khác không đáng tin cậy để kiểm soát luồng, bất kỳ mã nào tương tự
while True:
try:
task = pending_queue.get_nowait()
except queue.Empty:
break
là không có thật. Điều này sẽ giết nhân viên ngay cả khi mili giây sau đó một nhiệm vụ khác xuất hiện trong hàng đợi. Công nhân sẽ không phục hồi và sau một lúc TẤT CẢ công nhân sẽ biến mất khi họ ngẫu nhiên tìm thấy hàng đợi trống trong giây lát. Kết quả cuối cùng sẽ là hàm đa xử lý chính (hàm có phép nối () trên các quy trình) sẽ trả về mà không cần hoàn thành tất cả các tác vụ. Đẹp. Chúc may mắn gỡ lỗi thông qua đó nếu bạn có hàng nghìn nhiệm vụ và một vài nhiệm vụ bị thiếu.
Vấn đề khác là việc sử dụng các giá trị sentinel. Nhiều người đã đề xuất thêm một giá trị sentinel trong hàng đợi để gắn cờ kết thúc hàng đợi. Nhưng để gắn cờ chính xác cho ai? Nếu có N công nhân, giả sử N là số lõi có sẵn cho hoặc nhận, thì một giá trị sentinel duy nhất sẽ chỉ gắn cờ kết thúc hàng đợi cho một công nhân. Tất cả các công nhân khác sẽ ngồi chờ thêm việc khi không còn việc nào. Ví dụ điển hình mà tôi đã thấy là
while True:
task = pending_queue.get()
if task == SOME_SENTINEL_VALUE:
break
Một công nhân sẽ nhận được giá trị sentinel trong khi những người còn lại sẽ đợi vô thời hạn. Không có bài đăng nào tôi xem được đề cập rằng bạn cần phải gửi giá trị sentinel vào hàng đợi ÍT NHẤT bao nhiêu lần khi bạn có nhân viên để TẤT CẢ họ nhận được nó.
Vấn đề khác là việc xử lý các ngoại lệ trong quá trình thực thi tác vụ. Một lần nữa những điều này nên được bắt và quản lý. Hơn nữa, nếu bạn cócompleted_tasks
hàng đợi, bạn nên đếm một cách độc lập theo cách xác định có bao nhiêu mục trong hàng đợi trước khi bạn quyết định rằng công việc đã hoàn thành. Một lần nữa dựa vào kích thước hàng đợi chắc chắn sẽ thất bại và trả về kết quả không mong muốn.
Trong ví dụ bên dưới, par_proc()
hàm sẽ nhận một danh sách các tác vụ bao gồm các hàm mà các tác vụ này sẽ được thực thi cùng với bất kỳ đối số và giá trị nào được đặt tên.
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
Và đây là một bài kiểm tra để chạy đoạn mã trên với
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
cộng với một cái khác với một số ngoại lệ
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
Hy vọng điều đó là hữu ích.