
本文介绍如何在 Jupyter Notebook 中,使用 ThreadPool 替代 multiprocessing,并结合 tqdm 的 position 参数,为多个并发运行的外部程序(如 program.exe)分别显示实时、互不干扰的进度条。
本文介绍如何在 jupyter notebook 中,使用 threadpool 替代 multiprocessing,并结合 tqdm 的 `position` 参数,为多个并发运行的外部程序(如 program.exe)分别显示实时、互不干扰的进度条。
在 Python 中实现并发执行外部程序并实时追踪其内部迭代进度,常因进程隔离与标准输出重定向问题导致 tqdm 失效——尤其在 Jupyter 环境下,multiprocessing 子进程的标准输出默认不回传至 notebook 单元,且 tqdm 进度条无法跨进程共享或安全序列化(如报错 TypeError: cannot pickle '_hashlib.HMAC' object)。根本解法不是强行“修复” multiprocessing + tqdm,而是选用更轻量、线程安全且与 tqdm 兼容性更好的 ThreadPool。
ThreadPool 与 multiprocessing.Pool 的关键区别在于:线程共享同一进程的内存空间和 stdout/stderr,因此 tqdm.tqdm(..., position=n) 可在主线程上下文中稳定渲染多个垂直堆叠的进度条;同时,它完全支持 Jupyter 的 rich 输出协议(包括动态刷新),无需额外配置即可在 notebook 单元内原生显示。
此外,应避免在子函数中调用 os.chdir() ——这不仅引发竞态风险,还违背进程/线程隔离原则。正确做法是利用 subprocess.Popen 的 cwd 参数直接指定工作目录:
本文档主要讲述的是多线程技术在iOS开发中的使用;所谓进程对应的是一个应用程序,负责开辟内存空间供应用程序使用,但是进程不能执行任务(指令)。一个进程至少包含一条线程,线程是程序的执行流。 iOS程序启动时,在创建一个进程的同时, 会开始运行一个线程,该线程被称为主线程;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
import subprocess
from multiprocessing.pool import ThreadPool
import tqdm
import time
def run_exe_instance(args):
# args 示例: (filename, num_iters, working_dir)
filename, num_iters, working_dir = args
# ✅ 正确:通过 cwd 参数指定子进程工作目录,无需 os.chdir
proc = subprocess.Popen(
["program.exe"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
cwd=working_dir
)
# ✅ 使用 tqdm.tqdm 并指定唯一 position(需保证不重复)
with tqdm.tqdm(
total=num_iters,
desc=f"Running {filename}",
position=args[1] % 10, # 示例:限制 position 范围防溢出
leave=True,
dynamic_ncols=True
) as pbar:
for i in range(num_iters):
# 模拟等待程序输出一行(实际中可 readline() 解析)
if proc.poll() is None: # 程序仍在运行
time.sleep(0.5) # 或替换为:line = proc.stdout.readline().strip()
pbar.update(1)
# 等待子进程彻底结束(可选)
proc.wait()
print(f"✅ {filename} completed.")
# 主执行逻辑(Jupyter 兼容写法)
if __name__ == "__main__":
__spec__ = None # Jupyter 兼容性补丁
# 构造参数列表:每个元组对应一个任务实例
task_args = [
("instance_1", 50, "/path/to/dir1"),
("instance_2", 30, "/path/to/dir2"),
("instance_3", 40, "/path/to/dir3"),
]
with ThreadPool(len(task_args)) as pool:
pool.starmap(run_exe_instance, task_args)
⚠️ 重要注意事项:
- position 值必须全局唯一且非负整数(推荐 0, 1, 2...),否则进度条会覆盖;若任务数动态变化,建议用 enumerate() 动态分配位置;
- leave=True 确保任务完成后进度条保留最终状态(设为 False 则自动清除);
- 在真实场景中,应将 time.sleep() 替换为对 proc.stdout.readline() 的阻塞/非阻塞读取,并根据程序实际输出节奏更新进度条(例如每收到一行日志即 pbar.update(1));
- 若外部程序无规律输出,可结合 proc.stdout.read(1) 或 select.select() 实现无等待轮询(Windows 下需额外处理);
- 切勿在 multiprocessing 中尝试传递 tqdm 对象或锁/信号量等不可序列化对象——这是设计层面的限制,而非配置问题。
综上,面向 I/O 密集型外部程序调用(如运行 CLI 工具、仿真器等),ThreadPool + tqdm.tqdm(position=n) 是 Jupyter 环境下最简洁、可靠、可调试的进度可视化方案。










