守护进程模式
使用python开发后台服务程序的时候,每次修改代码之后都需要重启服务才能生效比较麻烦。
看了一下Python开源的Web框架(Django、Flask等)都有自己的自动加载模块功能(autoreload.py),都是通过subprocess模式创建子进程,主进程作为守护进程,子进程中一个线程负责检测文件是否发生变化,如果发生变化则退出,主进程检查子进程的退出码(exist code)如果与约定的退出码一致,则重新启动一个子进程继续工作。
自动重新加载模块代码如下:
autoreload.py
#!/usr/bin/env python # -*- coding: utf-8 -*- """This module is used to test how to reload the modules automatically when any changes is detected. """ __author__="Wenjun Xiao" import os,sys,time,subprocess,thread def iter_module_files(): for module in sys.modules.values(): filename = getattr(module, '__file__', None) if filename: if filename[-4:] in ('.pyo', '.pyc'): filename = filename[:-1] yield filename def is_any_file_changed(mtimes): for filename in iter_module_files(): try: mtime = os.stat(filename).st_mtime except IOError: continue old_time = mtimes.get(filename, None) if old_time is None: mtimes[filename] = mtime elif mtime > old_time: return 1 return 0 def start_change_detector(): mtimes = {} while 1: if is_any_file_changed(mtimes): sys.exit(3) time.sleep(1) def restart_with_reloader(): while 1: args = [sys.executable] + sys.argv new_env = os.environ.copy() new_env['RUN_FLAG'] = 'true' exit_code = subprocess.call(args, env=new_env) if exit_code != 3: return exit_code def run_with_reloader(runner): if os.environ.get('RUN_FLAG') == 'true': thread.start_new_thread(runner, ()) try: start_change_detector() except KeyboardInterrupt: pass else: try: sys.exit(restart_with_reloader()) except KeyboardInterrupt: pass
测试的主模块如下:
runner.py
#!/usr/bin/env python # -*- coding: utf-8 -*- """Runner for testing autoreload module.""" __author__="Wenjun Xiao" import os,time def runner(): print "[%s]enter..." % os.getpid() while 1: time.sleep(1) print "[%s]runner." % os.getpid() if __name__ == '__main__': from autoreload import run_with_reloader run_with_reloader(runner)
运行runner.py:
promissing@ubuntu:python-autoreload$ python runner.py
[11743]enter...
主程序已经运行,只不过是一致在循环,可以查看此时有两个进程:
promissing@ubuntu:~$ ps -aux|grep runner[.py] promiss+ 11742 0.0 0.2 10928 4208 pts/0 S+ 19:34 0:00 python runner.py promiss+ 11743 0.0 0.1 20152 4092 pts/0 Sl+ 19:34 0:00 /usr/bin/python runner.py
在编辑器中打开runner.py做一些可见的修改(增加一条打印语句)如下:
# runner.py ... def runner(): print "[%s]enter..." % os.getpid() print "[%s]Runner has changed." % os.getpid() while 1: time.sleep(1) print "[%s]runner." % os.getpid() ...
保存之后查看运行运行情况:
promissing@ubuntu:python-autoreload$ python runner.py [11743]enter... [11772]enter... [11772]Runner has changed.
可以看到新增的语句已经生效,继续看进程情况:
promissing@ubuntu:~$ ps -aux|grep runner[.py] promiss+ 11742 0.0 0.2 10928 4220 pts/0 S+ 19:34 0:00 python runner.py promiss+ 11772 0.0 0.1 20152 4092 pts/0 Sl+ 19:37 0:00 /usr/bin/python runner.py
可以对比两次的进程,可以看到使用守护进程模式可以简单的实现模块自动重新加载功能。
使用守护进程模式,有一种情况比较麻烦:如果主进程由于其他原因退出了,那么子进程还在运行:
promissing@ubuntu:~$ kill 11742 promissing@ubuntu:~$ ps -aux|grep runner[.py] promiss+ 11772 0.0 0.1 20152 4092 pts/0 Sl 19:37 0:00 /usr/bin/python runner.py
为了重启服务还需要通过其他方式找到子进程并结束它可以。
守护进程模式-退出问题
为了解决由于守护进程退出,而导致子进程没有退出的问题,一种比较简单的解决方法就是在守护进程退出的时候也把子进程结束:
# autoreload.py ... import signal ... _sub_proc = None def signal_handler(*args): global _sub_proc if _sub_proc: print "[%s]Stop subprocess:%s" % (os.getpid(), _sub_proc.pid) _sub_proc.terminate() sys.exit(0) def restart_with_reloader(): signal.signal(signal.SIGTERM, signal_handler) while 1: args = [sys.executable] + sys.argv new_env = os.environ.copy() new_env['RUN_FLAG'] = 'true' global _sub_proc _sub_proc = subprocess.Popen(args, env=new_env) exit_code = _sub_proc.wait() if exit_code != 3: return exit_code ...
运行,查看效果(这次没有测试修改):
promissing@ubuntu:python-autoreload$ python runner.py [12425]enter... [12425]Runner has changed. [12424]Stop subprocess:12425
另一个控制台执行的命令如下:
promissing@ubuntu:~$ ps -aux|grep runner[.py] promiss+ 12424 0.2 0.2 10928 4224 pts/0 S+ 20:26 0:00 python runner.py promiss+ 12425 0.2 0.1 20152 4092 pts/0 Sl+ 20:26 0:00 /usr/bin/python runner.py promissing@ubuntu:~$ kill 12424 promissing@ubuntu:~$ ps -aux|grep runner[.py] promissing@ubuntu:~$
已经达到我们需要的功能了吗?等等,在控制台上运行工程总是能很好的工作,如果是在IDE中呢?由于IDE中输入输出是重定向处理的,比如,在Sublime中就没有办法获取到输出信息。
因此还需要进一步完善输出的问题。
守护进程模式-输出问题
解决输出问题,也很简单,修改如下:
# autoreload.py ... def restart_with_reloader(): signal.signal(signal.SIGTERM, signal_handler) while 1: args = [sys.executable] + sys.argv new_env = os.environ.copy() new_env['RUN_FLAG'] = 'true' global _sub_proc _sub_proc = subprocess.Popen(args, env=new_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) read_stdout(_sub_proc.stdout) exit_code = _sub_proc.wait() if exit_code != 3: return exit_code ... def read_stdout(stdout): while 1: data = os.read(stdout.fileno(), 2**15) if len(data) > 0: sys.stdout.write(data) else: stdout.close() sys.stdout.flush() break
经过以上修改,也适合在IDE中使用守护进程模式了。
源代码:https://github.com/wenjunxiao/python-autoreload
以上这篇Python自动重新加载模块详解(autoreload module)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]