이 글은 주로 Python 데몬과 스크립트 싱글턴 연산을 소개합니다. 편집자는 꽤 좋다고 생각해서 지금 공유하고 참고용으로 제공하겠습니다. 편집기를 따라 살펴보겠습니다
1. 소개
데몬 프로세스의 가장 중요한 특징은 백그라운드에서 실행되어야 한다는 것입니다. 이러한 환경에는 닫히지 않은 파일 설명자, 제어 터미널, 세션 및 프로세스 그룹, 작업 디렉터리, 파일 생성 마스크 등이 포함됩니다. 시스템이 시작될 때 시작 스크립트 /etc/rc.d에서 시작할 수 있습니다. inetd 데몬에 의해 시작될 수 있습니다. 작업 계획 프로세스인 crond에 의해 시작되거나 사용자 터미널(보통 셸)에 의해 실행될 수도 있습니다.
Python에서는 데이터 충돌을 피하기 위해 스크립트 인스턴스가 하나만 실행되도록 해야 하는 경우가 있습니다.
2. Python 데몬 프로세스
1. 함수 구현
#!/usr/bin/env python #coding: utf-8 import sys, os '''将当前进程fork为一个守护进程 注意:如果你的守护进程是由inetd启动的,不要这样做!inetd完成了 所有需要做的事情,包括重定向标准文件描述符,需要做的事情只有chdir()和umask()了 ''' def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): #重定向标准文件描述符(默认情况下定向到/dev/null) try: pid = os.fork() #父进程(会话组头领进程)退出,这意味着一个非会话组头领进程永远不能重新获得控制终端。 if pid > 0: sys.exit(0) #父进程退出 except OSError, e: sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) ) sys.exit(1) #从母体环境脱离 os.chdir("/") #chdir确认进程不保持任何目录于使用状态,否则不能umount一个文件系统。也可以改变到对于守护程序运行重要的文件所在目录 os.umask(0) #调用umask(0)以便拥有对于写的任何东西的完全控制,因为有时不知道继承了什么样的umask。 os.setsid() #setsid调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。 #执行第二次fork try: pid = os.fork() if pid > 0: sys.exit(0) #第二个父进程退出 except OSError, e: sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) ) sys.exit(1) #进程已经是守护进程了,重定向标准文件描述符 for f in sys.stdout, sys.stderr: f.flush() si = open(stdin, 'r') so = open(stdout, 'a+') se = open(stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) #dup2函数原子化关闭和复制文件描述符 os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) #示例函数:每秒打印一个数字和时间戳 def main(): import time sys.stdout.write('Daemon started with pid %d\n' % os.getpid()) sys.stdout.write('Daemon stdout output\n') sys.stderr.write('Daemon stderr output\n') c = 0 while True: sys.stdout.write('%d: %s\n' %(c, time.ctime())) sys.stdout.flush() c = c+1 time.sleep(1) if __name__ == "__main__": daemonize('/dev/null','/tmp/daemon_stdout.log','/tmp/daemon_error.log') main()
ps -ef | grep daemon 명령을 통해 백그라운드에서 실행되는 상속을 볼 수 있습니다. py에서 /tmp/daemon_error.log는 오류 작업 로그를 기록하고 /tmp/daemon_stdout.log는 표준 출력 로그를 기록합니다.
2. 클래스 구현
#!/usr/bin/env python #coding: utf-8 #python模拟linux的守护进程 import sys, os, time, atexit, string from signal import SIGTERM class Daemon: def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): #需要获取调试信息,改为stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr',以root身份运行。 self.stdin = stdin self.stdout = stdout self.stderr = stderr self.pidfile = pidfile def _daemonize(self): try: pid = os.fork() #第一次fork,生成子进程,脱离父进程 if pid > 0: sys.exit(0) #退出主进程 except OSError, e: sys.stderr.write('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror)) sys.exit(1) os.chdir("/") #修改工作目录 os.setsid() #设置新的会话连接 os.umask(0) #重新设置文件创建权限 try: pid = os.fork() #第二次fork,禁止进程打开终端 if pid > 0: sys.exit(0) except OSError, e: sys.stderr.write('fork #2 failed: %d (%s)\n' % (e.errno, e.strerror)) sys.exit(1) #重定向文件描述符 sys.stdout.flush() sys.stderr.flush() si = file(self.stdin, 'r') so = file(self.stdout, 'a+') se = file(self.stderr, 'a+', 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) #注册退出函数,根据文件pid判断是否存在进程 atexit.register(self.delpid) pid = str(os.getpid()) file(self.pidfile,'w+').write('%s\n' % pid) def delpid(self): os.remove(self.pidfile) def start(self): #检查pid文件是否存在以探测是否存在进程 try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: message = 'pidfile %s already exist. Daemon already running!\n' sys.stderr.write(message % self.pidfile) sys.exit(1) #启动监控 self._daemonize() self._run() def stop(self): #从pid文件中获取pid try: pf = file(self.pidfile,'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if not pid: #重启不报错 message = 'pidfile %s does not exist. Daemon not running!\n' sys.stderr.write(message % self.pidfile) return #杀进程 try: while 1: os.kill(pid, SIGTERM) time.sleep(0.1) #os.system('hadoop-daemon.sh stop datanode') #os.system('hadoop-daemon.sh stop tasktracker') #os.remove(self.pidfile) except OSError, err: err = str(err) if err.find('No such process') > 0: if os.path.exists(self.pidfile): os.remove(self.pidfile) else: print str(err) sys.exit(1) def restart(self): self.stop() self.start() def _run(self): """ run your fun""" while True: #fp=open('/tmp/result','a+') #fp.write('Hello World\n') sys.stdout.write('%s:hello world\n' % (time.ctime(),)) sys.stdout.flush() time.sleep(2) if __name__ == '__main__': daemon = Daemon('/tmp/watch_process.pid', stdout = '/tmp/watch_stdout.log') if len(sys.argv) == 2: if 'start' == sys.argv[1]: daemon.start() elif 'stop' == sys.argv[1]: daemon.stop() elif 'restart' == sys.argv[1]: daemon.restart() else: print 'unknown command' sys.exit(2) sys.exit(0) else: print 'usage: %s start|stop|restart' % sys.argv[0] sys.exit(2)
실행 결과:
데몬을 설계할 때입니다. 템플릿은 데몬에서 다른 파일의 데몬을 가져온 다음 하위 클래스를 정의하고 run() 메서드를 재정의하여 자신만의 기능을 구현합니다.
class MyDaemon(Daemon): def run(self): while True: fp=open('/tmp/run.log','a+') fp.write('Hello World\n') time.sleep(1)
부적절: 신호 처리 signal.signal(signal.SIGTERM, cleanup_handler)이 아직 설치되지 않았으며, 등록 프로그램 종료 시 콜백 함수 delpid()가 호출되지 않습니다.
그런 다음 쉘 명령을 작성하고 시작 서비스를 추가하고 2초마다 데몬이 시작되는지 확인하고 그렇지 않으면 시작하고 복구 프로세스를 자동으로 모니터링합니다.
#/bin/sh while true do count=`ps -ef | grep "daemonclass.py" | grep -v "grep"` if [ "$?" != "0" ]; then daemonclass.py start fi sleep 2 done
3. Python은 하나의 스크립트 인스턴스만 실행할 수 있음을 보장합니다.
1. 파일 자체를 열고 잠급니다. 🎜>
#!/usr/bin/env python #coding: utf-8 import fcntl, sys, time, os pidfile = 0 def ApplicationInstance(): global pidfile pidfile = open(os.path.realpath(__file__), "r") try: fcntl.flock(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) #创建一个排他锁,并且所被锁住其他进程不会阻塞 except: print "another instance is running..." sys.exit(1) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)참고: open() 매개변수는 w를 사용할 수 없습니다. 그렇지 않으면 파일 자체를 덮어쓰게 됩니다. pidfile은 전역 변수로 선언되어야 합니다. 그렇지 않으면 수명 주기가 지역 변수의 종료되고 파일 설명자가 참조됩니다. 개수는 0이고 시스템에 의해 재활용됩니다(전체 함수가 주 함수에 작성된 경우 전역으로 정의할 필요가 없습니다). 2. 사용자 정의 파일을 열고 잠급니다.
#!/usr/bin/env python #coding: utf-8 import fcntl, sys, time pidfile = 0 def ApplicationInstance(): global pidfile pidfile = open("instance.pid", "w") try: fcntl.lockf(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) #创建一个排他锁,并且所被锁住其他进程不会阻塞 except IOError: print "another instance is running..." sys.exit(0) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)3. file
#!/usr/bin/env python #coding: utf-8 import time, os, sys import signal pidfile = '/tmp/process.pid' def sig_handler(sig, frame): if os.path.exists(pidfile): os.remove(pidfile) sys.exit(0) def ApplicationInstance(): signal.signal(signal.SIGTERM, sig_handler) signal.signal(signal.SIGINT, sig_handler) signal.signal(signal.SIGQUIT, sig_handler) try: pf = file(pidfile, 'r') pid = int(pf.read().strip()) pf.close() except IOError: pid = None if pid: sys.stdout.write('instance is running...\n') sys.exit(0) file(pidfile, 'w+').write('%s\n' % os.getpid()) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)4. 특정 폴더 또는 파일 검색
#!/usr/bin/env python #coding: utf-8 import time, commands, signal, sys def sig_handler(sig, frame): if os.path.exists("/tmp/test"): os.rmdir("/tmp/test") sys.exit(0) def ApplicationInstance(): signal.signal(signal.SIGTERM, sig_handler) signal.signal(signal.SIGINT, sig_handler) signal.signal(signal.SIGQUIT, sig_handler) if commands.getstatusoutput("mkdir /tmp/test")[0]: print "instance is running..." sys.exit(0) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)특정 파일을 감지하여 해당 파일이 존재하는지 확인할 수도 있습니다.
import os import os.path import time #class used to handle one application instance mechanism class ApplicationInstance: #specify the file used to save the application instance pid def __init__( self, pid_file ): self.pid_file = pid_file self.check() self.startApplication() #check if the current application is already running def check( self ): #check if the pidfile exists if not os.path.isfile( self.pid_file ): return #read the pid from the file pid = 0 try: file = open( self.pid_file, 'rt' ) data = file.read() file.close() pid = int( data ) except: pass #check if the process with specified by pid exists if 0 == pid: return try: os.kill( pid, 0 ) #this will raise an exception if the pid is not valid except: return #exit the application print "The application is already running..." exit(0) #exit raise an exception so don't put it in a try/except block #called when the single instance starts to save it's pid def startApplication( self ): file = open( self.pid_file, 'wt' ) file.write( str( os.getpid() ) ) file.close() #called when the single instance exit ( remove pid file ) def exitApplication( self ): try: os.remove( self.pid_file ) except: pass if __name__ == '__main__': #create application instance appInstance = ApplicationInstance( '/tmp/myapp.pid' ) #do something here print "Start MyApp" time.sleep(5) #sleep 5 seconds print "End MyApp" #remove pid file appInstance.exitApplication()위 OS .kill(pid, 0)은 pid가 있는 프로세스가 아직 살아 있는지 확인하는 데 사용됩니다. pid 프로세스가 중지된 경우 해당 프로세스가 실행 중이면 kill 신호가 전송되지 않습니다.
#!/usr/bin/env python #coding: utf-8 import socket, time, sys def ApplicationInstance(): try: global s s = socket.socket() host = socket.gethostname() s.bind((host, 60123)) except: print "instance is running..." sys.exit(0) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)이 기능은 쉽게 재사용할 수 있도록 데코레이터를 사용하여 구현할 수 있습니다. 효과는 위와 동일합니다. 동일):
#!/usr/bin/env python #coding: utf-8 import socket, time, sys import functools #使用装饰器实现 def ApplicationInstance(func): @functools.wraps(func) def fun(*args,**kwargs): import socket try: global s s = socket.socket() host = socket.gethostname() s.bind((host, 60123)) except: print('already has an instance...') return None return func(*args,**kwargs) return fun @ApplicationInstance def main(): while True: print 'running...' time.sleep(1) if __name__ == "__main__": main()4. 요약