使用apply_async与tqdm时出现多余空进度条问题排查
多进程Pool结合tqdm出现多行空进度条问题排查
我写了一段代码,用两个独立的multiprocessing Pool处理多份表格,想用tqdm进度条展示处理进度。现在进度展示功能能跑,但出现了多行进度为0的空进度条,找不到问题根源,求帮忙定位哪里错了?
代码
from multiprocessing import Pool, Manager from tqdm import * from collections import deque pbar = tqdm(position=0) def task(tables_started, right_list, left_list, current_table, process): if current_table not in tables_started: tables_started.append(current_table) if process == "left_process": left_list.append(current_table) else: right_list.append(current_table) # print(f"Starting {current_table} running on {process}.") return True else: return False def return_callback(result): # global pbar # print(result) if result: # print("updating") pbar.update() else: # print("not updating") pass if __name__ == '__main__': right_tables = deque() manager = Manager() for i in range(10): right_tables.append("Table" + str(i)) left_tables = right_tables.copy() left_tables.reverse() # print(left_tables) # print(right_tables) tables_started = manager.list() right_list = manager.list() left_list = manager.list() left_pool = Pool(processes=2) right_pool = Pool(processes=2) print(f"left_tables: {left_tables}") print(f"right_tables: {right_tables}") pbar.total = len(left_tables) while left_tables: left_table = left_tables.pop() left_res = left_pool.apply_async(task, (tables_started, right_list, left_list, left_table, "left_process"), callback=return_callback) right_table = right_tables.pop() right_res = right_pool.apply_async(task, (tables_started, right_list, left_list, right_table, "right_process"), callback=return_callback) left_pool.close() right_pool.close() left_pool.join() right_pool.join() # print(f"right_list: {right_list}") # print(f"left_list: {left_list}")
终端现象
终端中出现多条显示0%的空进度条,仅一条进度条正常更新处理进度。
问题根源
- 全局初始化进度条导致子进程复制实例:你在全局作用域创建了
tqdm的pbar对象,而multiprocessing Pool启动子进程时会fork父进程的内存空间,每个子进程都会复制这个pbar实例。这些子进程的pbar没有被正确更新,就会在终端输出空的0%进度条。 - 任务重复处理导致无效回调:
left_tables是right_tables反转后的副本,两者包含完全相同的表格数据,因此一半的任务会因表格已被处理返回False,不会触发进度条更新,进一步凸显了子进程空进度条的问题。
修复方案
- 将进度条初始化移到主进程块内:避免子进程复制多余的进度条实例。
- 让回调函数正确绑定主进程的进度条:通过闭包或参数传递确保回调只更新主进程的那一条进度条。
- 可选:优化任务逻辑避免重复处理:如果你的需求是处理不同表格,需调整
left_tables和right_tables的生成逻辑。
修改后的代码:
from multiprocessing import Pool, Manager from tqdm import tqdm from collections import deque def task(tables_started, right_list, left_list, current_table, process): if current_table not in tables_started: tables_started.append(current_table) if process == "left_process": left_list.append(current_table) else: right_list.append(current_table) return True else: return False def return_callback(result, pbar): if result: pbar.update() if __name__ == '__main__': right_tables = deque() manager = Manager() for i in range(10): right_tables.append("Table" + str(i)) left_tables = right_tables.copy() left_tables.reverse() tables_started = manager.list() right_list = manager.list() left_list = manager.list() left_pool = Pool(processes=2) right_pool = Pool(processes=2) print(f"left_tables: {left_tables}") print(f"right_tables: {right_tables}") # 主进程内初始化进度条,设置总任务数 total_tasks = len(left_tables) pbar = tqdm(total=total_tasks, position=0) while left_tables: left_table = left_tables.pop() # 通过lambda将主进程的pbar传给回调 left_res = left_pool.apply_async( task, (tables_started, right_list, left_list, left_table, "left_process"), callback=lambda res: return_callback(res, pbar) ) right_table = right_tables.pop() right_res = right_pool.apply_async( task, (tables_started, right_list, left_list, right_table, "right_process"), callback=lambda res: return_callback(res, pbar) ) left_pool.close() right_pool.close() left_pool.join() right_pool.join() # 关闭进度条,清理终端输出 pbar.close()
额外说明
- 主进程内初始化的
pbar不会被子进程复制,所有回调操作的都是同一个进度条实例。 - 用
lambda传递pbar可以确保回调函数能正确引用主进程的进度条。 - 调用
pbar.close()能确保任务结束后终端输出干净,避免残留进度条状态。
内容的提问来源于stack exchange,提问作者bakaneko
相关产品推荐
相关产品推荐

