第二段实习
泛泛地谈一些心得和方法论的东西,外加一些通用的宽泛知识。
存在 AIGC,但是它们都经过验证。
并行任务处理
关于此内容的 C++ 方式,可翻阅本博客的其他文章。
如何监控和管理子进程
在 Python 中,subprocess 模块是执行外部命令和管理子进程的标准库。
启动进程:
subprocess.run(): 这是一个高级函数,它会启动一个命令,等待它完成,然后返回一个包含其退出码、标准输出和标准错误的CompletedProcess对象。这是一个阻塞操作,在子进程结束前,你的 Python 脚本会一直等待。subprocess.Popen(): 这是一个更底层的接口,它会立即启动一个命令而不会等待它完成。它返回一个Popen对象,你可以用这个对象来与进程交互,比如等待它 (.wait())、检查它的状态 (.poll())、或者读取它的输出流。这是实现并行处理的基础。
监控进程是否结束:
process.wait(): 这是一个阻塞方法。调用它会暂停你的脚本,直到这个特定的子进程结束。process.poll(): 这是一个非阻塞方法。调用它会立即返回进程的状态:如果进程仍在运行,返回None;如果进程已经结束,返回它的退出码(通常0表示成功)。你可以在一个循环中反复调用.poll()来检查多个进程的状态,而不会阻塞主程序。
获取进程的输出:
- 在创建
Popen对象时,你可以将stdout和stderr参数设置为subprocess.PIPE。这会将子进程的标准输出和标准错误重定向到管道,你的 Python 脚本可以像读取文件一样从中读取内容。 process.communicate(): 这个方法会等待进程结束,然后一次性读取并返回所有的stdout和stderr内容。这是最简单、最安全的读取输出的方式,因为它能避免管道被填满导致的死锁问题。
- 在创建
并发写入文件的互斥问题分析
多个进程同时写入一个文件是否需要加锁?
答案是:需要。 如果没有同步机制,多个进程可能在几乎完全相同的时间点尝试写入文件,导致:
- 内容交错。
- 数据丢失: 在某些文件系统和操作下,并发写入甚至可能导致部分数据丢失。
解决方案: 使用锁 (Lock)。在 Python 的
multiprocessing模块中,可以创建一个Lock对象。任何进程在写入文件前,必须先获取 (acquire()) 这个锁。当它持有锁时,其他任何尝试获取锁的进程都会被阻塞,直到该进程释放 (release()) 锁。这确保了在任何时刻,只有一个进程能够执行写入文件的代码块。
使用 multiprocessing.Pool
对于你描述的“固定数量的进程池”任务,multiprocessing.Pool 是最优雅、最高效的解决方案。它为你封装了所有复杂的进程管理逻辑:
- 你创建一个
Pool并指定工作进程的数量 (use_core)。 - 你给它一个任务列表(我们要运行的命令)。
Pool会自动启动工作进程,从任务列表中取出任务并执行。- 它会自动维护一个正在运行的进程队列,一旦有进程完成,它会立刻从任务列表中取下一个任务来执行,直到所有任务都完成。
集群并行作业调度
我的同事:
你们跑
某个命令的时候,是直接敲的吗?我发现用bsub -q short -Is 某个命令会快一些,也不影响工作站其他任务。
解释
简单来说,同事的建议是利用集群管理系统来运行实验,而不是直接在登录节点(你登录的服务器)上运行。这是一种非常好的实践,特别是在多人共享的服务器或计算集群环境中。
bsub 是一个命令,它属于一个叫做 LSF (Load Sharing Facility) 的作业调度系统。你可以把它想象成一个“任务分发官”。
在很多大学、研究机构和公司里,计算资源(服务器/工作站)是被很多人共享的。为了公平、高效地分配计算资源,管理员会安装 LSF 这样的系统。你不能直接登录到一台机器上就占用所有 CPU 和内存跑程序,而是要把你的任务“提交”给 LSF,LSF 会根据当前系统负载情况,找一台合适的、空闲的机器去执行你的任务。
命令拆解:bsub -q short -Is 某个命令
bsub: 这是核心命令,意思是“我要提交一个作业(job)”。-q short:-q代表--queue(队列)。short是队列的名字。- 管理员通常会根据任务的特性(比如预计运行时长、需要的资源多少)创建不同的队列。
short队列通常意味着为“短时间运行”的任务准备的,可能会有更高的优先级或者能更快地被调度。
-Is: 这是两个参数的组合,非常关键。-I: 代表 Interactive (交互式)。这个参数告诉 LSF,这不是一个提交后就不用管的后台任务。我需要像在普通终端里一样,能实时看到程序的输出,并且程序结束后马上返回到我的命令行。如果没有-I,bsub会把任务提交到后台,你只能通过其他命令或查看日志文件来了解运行情况。-s: 代表 pseudo-terminal (伪终端)。这个参数配合-I使用,可以确保你的交互式任务拥有一个合适的终端环境,这样像ns-3运行时的进度条、颜色输出等都能正常显示。
某个命令: 这就是你要 LSF 系统为你执行的真正命令。
所以,整个命令的白话翻译就是:
“你好 LSF 系统,请帮我启动一个交互式的任务,把它放到 short 这个队列里去排队。我希望这个任务能有个终端让我看实时输出。你要执行的命令是
某个命令。”
为什么这样做更好?
“会快一些”。
这可能有两方面原因:
- 分配到更强的节点:当你直接在登录服务器上运行命令时,你用的是这台登录服务器的 CPU 和内存。登录服务器通常是给用户登录、编辑代码、管理文件用的,计算性能不一定强,而且可能有很多人在同时使用,资源紧张。而当你用
bsub提交任务时,LSF 会从整个集群里为你挑选一个专门用于计算的节点(Compute Node)。这些计算节点通常配置更高(更多的 CPU核心、更大的内存),并且当前可能更空闲,所以你的程序自然跑得更快。 - 避免资源争抢:登录服务器上可能运行着很多其他人的小进程,甚至是有人也在上面直接跑计算任务。你的程序需要和它们“抢”CPU 时间。而计算节点由 LSF 管理,会确保分配给你的资源(比如一个 CPU 核心)在任务期间是专属的,大大减少了干扰。
“不影响工作站其他任务”。
这是在共享环境中最重要的一点,也是体现专业素养的地方。
- 直接运行的坏处:如果你直接在登录服务器上运行一个计算密集型的仿真,它可能会瞬间吃掉大量的 CPU 和内存。这会导致:
- 服务器卡顿:所有正在这台服务器上工作的其他人(包括你自己)都会感觉到明显的卡顿,连敲一个
ls命令都可能要等半天。 - 系统不稳定:如果你的程序内存占用过大,可能会导致服务器因为内存耗尽而崩溃或杀死其他人的进程。
- 服务器卡顿:所有正在这台服务器上工作的其他人(包括你自己)都会感觉到明显的卡顿,连敲一个
- 使用
bsub的好处:你将计算压力从“公共的”登录服务器转移到了“专业的”计算节点上。登录服务器只负责处理你提交任务的这个请求,本身几乎没有负载。繁重的计算发生在后端专门的机器上,完全不会影响到登录服务器的响应速度和其他用户的正常工作。
总结
你的同事是在告诉你一个在共享计算环境中正确、高效且礼貌的工作方式。
| 操作方式 | 直接运行命令 | 通过 LSF 运行 (bsub ...) |
|---|---|---|
| 运行地点 | 你登录的那台服务器 (登录节点) | LSF 系统分配的某台服务器 (计算节点) |
| 资源 | 与服务器上所有人共享、抢占 | LSF 分配的专属、更优的资源 |
| 对他人影响 | 大。可能造成服务器卡顿,影响他人 | 小。不占用登录节点资源,不影响他人 |
| 效率 | 可能较慢,受他人影响大 | 通常更快,资源独占且配置更好 |
| 推荐度 | 不推荐 (在共享环境中) | 强烈推荐 |






