如何确保同一脚本(如Foo.pyx)同一时间仅运行一个实例?
确保Foo.pyx同一时间仅运行一个实例的几种方案
这是个非常常见的运维需求——不管是终止旧实例让新的运行,还是直接阻止新实例启动,核心都是避免多个实例同时占用资源。下面给你几个实用的实现思路,你可以根据自己的操作系统和具体需求选择:
方案1:用PID文件终止旧实例
这个思路是让脚本启动时先检查一个存放进程ID(PID)的文件(比如foo.pid):
- 如果文件存在,读取里面的PID,检查对应进程是否还在运行
- 如果进程存活,就杀掉它,然后更新PID文件为当前进程的PID
- 如果进程已经不存在(比如之前脚本意外崩溃),就直接覆盖PID文件
代码示例(Linux/macOS)
在Foo.pyx的最开头加入这段代码:
import os import signal PID_FILE = "foo.pid" def main(): # 检查PID文件 if os.path.exists(PID_FILE): with open(PID_FILE, 'r') as f: old_pid = int(f.read().strip()) # 检查旧进程是否存在 try: os.kill(old_pid, 0) # 发送0信号不终止进程,仅检查是否存在 print(f"发现旧实例(PID: {old_pid}),正在终止...") os.kill(old_pid, signal.SIGTERM) # 等待几秒确保进程退出,可选 import time time.sleep(2) # 再检查一次,没退出就强制杀 try: os.kill(old_pid, 0) os.kill(old_pid, signal.SIGKILL) except OSError: pass except OSError: # 旧进程不存在,删除无效PID文件 os.remove(PID_FILE) # 写入当前PID到文件 with open(PID_FILE, 'w') as f: f.write(str(os.getpid())) # 这里写你原来的脚本逻辑 print("脚本开始运行...") # 模拟长时间运行 import time time.sleep(30) if __name__ == "__main__": try: main() finally: # 脚本退出时删除PID文件(可选,避免意外残留) if os.path.exists(PID_FILE): try: with open(PID_FILE, 'r') as f: if int(f.read().strip()) == os.getpid(): os.remove(PID_FILE) except: pass
注意:Windows系统下os.kill的行为略有不同,需要用signal.CTRL_C_EVENT或者第三方库终止进程,后面的方案3会更适合跨平台场景。
方案2:用文件锁阻止新实例启动
如果你的需求是让旧实例继续运行,新实例直接退出,那么用系统级文件锁更可靠——因为PID文件可能因为脚本崩溃残留,而文件锁会在进程退出时自动释放。
跨平台代码示例
import os import sys LOCK_FILE = "foo.lock" def acquire_lock(): try: # 打开文件,模式为读写+创建,不截断 fd = os.open(LOCK_FILE, os.O_RDWR | os.O_CREAT | os.O_EXCL) os.close(fd) return True except OSError: # 文件已存在,说明已有实例在运行 return False def main(): if not acquire_lock(): print("已有实例在运行,新实例退出...") sys.exit(1) try: # 原脚本逻辑 print("脚本开始运行...") import time time.sleep(30) finally: # 退出时删除锁文件 if os.path.exists(LOCK_FILE): os.remove(LOCK_FILE) if __name__ == "__main__": main()
进阶优化(Linux/macOS):可以用fcntl的独占锁,不需要删除文件,进程退出自动释放:
import fcntl import os import sys LOCK_FILE = "foo.lock" def main(): fd = open(LOCK_FILE, 'w') try: fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) # 非阻塞独占锁 except BlockingIOError: print("已有实例在运行,新实例退出...") sys.exit(1) # 原脚本逻辑 print("脚本开始运行...") import time time.sleep(30) if __name__ == "__main__": main()
方案3:用psutil检查进程(更可靠的跨平台方案)
上面的PID文件方案有个小问题:如果旧进程退出后,系统分配了相同的PID给其他进程,会误杀无辜。用psutil可以准确检查PID对应的进程是不是你的脚本。
步骤:
- 先安装psutil:
pip install psutil - 在脚本中加入检查逻辑
代码示例(跨平台,终止旧实例)
import os import signal import sys import psutil PID_FILE = "foo.pid" SCRIPT_NAME = "Foo.pyx" def is_my_script_running(pid): try: proc = psutil.Process(pid) # 检查进程的命令行是否包含脚本名称 return SCRIPT_NAME in ' '.join(proc.cmdline()) except (psutil.NoSuchProcess, psutil.AccessDenied): return False def main(): if os.path.exists(PID_FILE): with open(PID_FILE, 'r') as f: old_pid = int(f.read().strip()) if is_my_script_running(old_pid): print(f"发现旧实例(PID: {old_pid}),正在终止...") try: proc = psutil.Process(old_pid) proc.terminate() proc.wait(timeout=3) except (psutil.NoSuchProcess, psutil.TimeoutExpired): proc.kill() else: # PID被复用,删除无效文件 os.remove(PID_FILE) # 写入当前PID with open(PID_FILE, 'w') as f: f.write(str(os.getpid())) try: # 原脚本逻辑 print("脚本开始运行...") import time time.sleep(30) finally: # 清理PID文件 if os.path.exists(PID_FILE): try: with open(PID_FILE, 'r') as f: if int(f.read().strip()) == os.getpid(): os.remove(PID_FILE) except: pass if __name__ == "__main__": main()
这个方案可以准确识别自己的脚本进程,避免误杀,而且跨Windows、Linux、macOS都能用。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

