当前位置:首页 > PHP教程 > php高级应用 > 列表

Python守护进程和脚本单例运行详解

发布:smiling 来源: PHP粉丝网  添加日期:2018-11-01 14:11:29 浏览: 评论:0 

本篇文章主要介绍了Python守护进程和脚本单例运行,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

一、简介

守护进程最重要的特性是后台运行;它必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等;它可以在系统启动时从启动脚本/etc/rc.d中启动,可以由inetd守护进程启动,也可以有作业规划进程crond启动,还可以由用户终端(通常是shell)执行。

Python有时需要保证只运行一个脚本实例,以避免数据的冲突。

二、Python守护进程

1、函数实现

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importsys, os 
  4.    
  5. '''将当前进程fork为一个守护进程 
  6.   注意:如果你的守护进程是由inetd启动的,不要这样做!inetd完成了 
  7.   所有需要做的事情,包括重定向标准文件描述符,需要做的事情只有chdir()和umask()了 
  8. ''
  9.    
  10. defdaemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
  11.    #重定向标准文件描述符(默认情况下定向到/dev/null) 
  12.   try:  
  13.     pid=os.fork()  
  14.      #父进程(会话组头领进程)退出,这意味着一个非会话组头领进程永远不能重新获得控制终端。 
  15.     ifpid >0: 
  16.       sys.exit(0) #父进程退出 
  17.   exceptOSError, e:  
  18.     sys.stderr.write ("fork #1 failed: (%d) %s\n"%(e.errno, e.strerror) ) 
  19.     sys.exit(1) 
  20.    
  21.    #从母体环境脱离 
  22.   os.chdir("/")#chdir确认进程不保持任何目录于使用状态,否则不能umount一个文件系统。也可以改变到对于守护程序运行重要的文件所在目录 
  23.   os.umask(0) #调用umask(0)以便拥有对于写的任何东西的完全控制,因为有时不知道继承了什么样的umask。 
  24.   os.setsid() #setsid调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。 
  25.    
  26.    #执行第二次fork 
  27.   try:  
  28.     pid=os.fork()  
  29.     ifpid >0: 
  30.       sys.exit(0) #第二个父进程退出 
  31.   exceptOSError, e:  
  32.     sys.stderr.write ("fork #2 failed: (%d) %s\n"%(e.errno, e.strerror) ) 
  33.     sys.exit(1) 
  34.    
  35.    #进程已经是守护进程了,重定向标准文件描述符 
  36.    
  37.   forfinsys.stdout, sys.stderr: f.flush() 
  38.   si=open(stdin,'r'
  39.   so=open(stdout,'a+'
  40.   se=open(stderr,'a+',0) 
  41.   os.dup2(si.fileno(), sys.stdin.fileno()) #dup2函数原子化关闭和复制文件描述符 
  42.   os.dup2(so.fileno(), sys.stdout.fileno()) 
  43.   os.dup2(se.fileno(), sys.stderr.fileno()) 
  44.    
  45. #示例函数:每秒打印一个数字和时间戳 
  46. defmain(): 
  47.   importtime 
  48.   sys.stdout.write('Daemon started with pid %d\n'%os.getpid()) 
  49.   sys.stdout.write('Daemon stdout output\n'
  50.   sys.stderr.write('Daemon stderr output\n'
  51.   c=0 
  52.   whileTrue: 
  53.     sys.stdout.write('%d: %s\n'%(c, time.ctime())) 
  54.     sys.stdout.flush() 
  55.     c=c+1 
  56.     time.sleep(1) 
  57.    
  58. if__name__=="__main__"
  59.    daemonize('/dev/null','/tmp/daemon_stdout.log','/tmp/daemon_error.log'
  60.    main() 

2、类实现

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3.    
  4. #python模拟linux的守护进程 
  5.    
  6. importsys, os, time, atexit, string 
  7. fromsignalimportSIGTERM 
  8.    
  9. classDaemon: 
  10.  def__init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
  11.    #需要获取调试信息,改为stdin='/dev/stdin', stdout='/dev/stdout', stderr='/dev/stderr',以root身份运行。 
  12.   self.stdin=stdin 
  13.   self.stdout=stdout 
  14.   self.stderr=stderr 
  15.   self.pidfile=pidfile 
  16.     
  17.  def_daemonize(self): 
  18.   try: 
  19.    pid=os.fork() #第一次fork,生成子进程,脱离父进程 
  20.    ifpid >0: 
  21.     sys.exit(0)  #退出主进程 
  22.   exceptOSError, e: 
  23.    sys.stderr.write('fork #1 failed: %d (%s)\n'%(e.errno, e.strerror)) 
  24.    sys.exit(1) 
  25.     
  26.   os.chdir("/")  #修改工作目录 
  27.   os.setsid()   #设置新的会话连接 
  28.   os.umask(0)   #重新设置文件创建权限 
  29.     
  30.   try: 
  31.    pid=os.fork()#第二次fork,禁止进程打开终端 
  32.    ifpid >0: 
  33.     sys.exit(0) 
  34.   exceptOSError, e: 
  35.    sys.stderr.write('fork #2 failed: %d (%s)\n'%(e.errno, e.strerror)) 
  36.    sys.exit(1) 
  37.     
  38.    #重定向文件描述符 
  39.   sys.stdout.flush() 
  40.   sys.stderr.flush() 
  41.   si=file(self.stdin,'r'
  42.   so=file(self.stdout,'a+'
  43.   se=file(self.stderr,'a+',0) 
  44.   os.dup2(si.fileno(), sys.stdin.fileno()) 
  45.   os.dup2(so.fileno(), sys.stdout.fileno()) 
  46.   os.dup2(se.fileno(), sys.stderr.fileno()) 
  47.     
  48.    #注册退出函数,根据文件pid判断是否存在进程 
  49.   atexit.register(self.delpid) 
  50.   pid=str(os.getpid()) 
  51.   file(self.pidfile,'w+').write('%s\n'%pid) 
  52.     
  53.  defdelpid(self): 
  54.   os.remove(self.pidfile) 
  55.    
  56.  defstart(self): 
  57.    #检查pid文件是否存在以探测是否存在进程 
  58.   try: 
  59.    pf=file(self.pidfile,'r'
  60.    pid=int(pf.read().strip()) 
  61.    pf.close() 
  62.   exceptIOError: 
  63.    pid=None 
  64.     
  65.   ifpid: 
  66.    message='pidfile %s already exist. Daemon already running!\n' 
  67.    sys.stderr.write(message%self.pidfile) 
  68.    sys.exit(1) 
  69.      
  70.   #启动监控 
  71.   self._daemonize() 
  72.   self._run() 
  73.    
  74.  defstop(self): 
  75.   #从pid文件中获取pid 
  76.   try: 
  77.    pf=file(self.pidfile,'r'
  78.    pid=int(pf.read().strip()) 
  79.    pf.close() 
  80.   exceptIOError: 
  81.    pid=None 
  82.     
  83.   ifnotpid: #重启不报错 
  84.    message='pidfile %s does not exist. Daemon not running!\n' 
  85.    sys.stderr.write(message%self.pidfile) 
  86.    return 
  87.    
  88.    #杀进程 
  89.   try: 
  90.    while1: 
  91.     os.kill(pid, SIGTERM) 
  92.     time.sleep(0.1) 
  93.     #os.system('hadoop-daemon.sh stop datanode'
  94.     #os.system('hadoop-daemon.sh stop tasktracker'
  95.     #os.remove(self.pidfile) 
  96.   exceptOSError, err: 
  97.    err=str(err) 
  98.    iferr.find('No such process') >0: 
  99.     ifos.path.exists(self.pidfile): 
  100.      os.remove(self.pidfile) 
  101.    else
  102.     printstr(err) 
  103.     sys.exit(1) 
  104.    
  105.  defrestart(self): 
  106.   self.stop() 
  107.   self.start() 
  108.    
  109.  def_run(self): 
  110.   """ run your fun""" 
  111.   whileTrue: 
  112.    #fp=open('/tmp/result','a+'
  113.    #fp.write('Hello World\n'
  114.    sys.stdout.write('%s:hello world\n'%(time.ctime(),)) 
  115.    sys.stdout.flush()  
  116.    time.sleep(2) 
  117.      
  118.    
  119. if__name__=='__main__'
  120.   daemon=Daemon('/tmp/watch_process.pid', stdout='/tmp/watch_stdout.log'
  121.   iflen(sys.argv)==2: 
  122.     if'start'==sys.argv[1]: 
  123.       daemon.start() 
  124.     elif'stop'==sys.argv[1]: 
  125.       daemon.stop() 
  126.     elif'restart'==sys.argv[1]: 
  127.       daemon.restart() 
  128.     else
  129.       print'unknown command' 
  130.       sys.exit(2) //phpfensi.com 
  131.     sys.exit(0) 
  132.   else
  133.     print'usage: %s start|stop|restart'%sys.argv[0] 
  134.     sys.exit(2) 

它是当Daemon设计成一个模板,在其他文件中from daemon import Daemon,然后定义子类,重写run()方法实现自己的功能。

  1. classMyDaemon(Daemon): 
  2.   defrun(self): 
  3.     whileTrue: 
  4.       fp=open('/tmp/run.log','a+'
  5.       fp.write('Hello World\n'
  6.       time.sleep(1) 

不足:信号处理signal.signal(signal.SIGTERM, cleanup_handler)暂时没有安装,注册程序退出时的回调函数delpid()没有被调用。

然后,再写个shell命令,加入开机启动服务,每隔2秒检测守护进程是否启动,若没有启动则启动,自动监控恢复程序。

  1. #/bin/sh 
  2. whiletrue 
  3. do 
  4.  count=`ps-ef |grep"daemonclass.py"|grep-v"grep"
  5.  if["$?"!="0"];then 
  6.    daemonclass.py start 
  7.  fi 
  8.  sleep2 
  9. done 

三、python保证只能运行一个脚本实例

1、打开文件本身加锁

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importfcntl, sys, time, os 
  4. pidfile=0 
  5.    
  6. defApplicationInstance(): 
  7.   globalpidfile 
  8.   pidfile=open(os.path.realpath(__file__),"r"
  9.   try: 
  10.     fcntl.flock(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB)#创建一个排他锁,并且所被锁住其他进程不会阻塞 
  11.   except: 
  12.     print"another instance is running..." 
  13.     sys.exit(1) 
  14.    
  15. if__name__=="__main__"
  16.   ApplicationInstance() 
  17.   whileTrue: 
  18.     print'running...' 
  19.     time.sleep(1) 

注意:open()参数不能使用w,否则会覆盖本身文件;pidfile必须声明为全局变量,否则局部变量生命周期结束,文件描述符会因引用计数为0被系统回收(若整个函数写在主函数中,则不需要定义成global)。

2、打开自定义文件并加锁

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importfcntl, sys, time 
  4. pidfile=0 
  5.    
  6. defApplicationInstance(): 
  7.   globalpidfile 
  8.   pidfile=open("instance.pid","w"
  9.   try: 
  10.     fcntl.lockf(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB)#创建一个排他锁,并且所被锁住其他进程不会阻塞 
  11.   exceptIOError: 
  12.     print"another instance is running..." 
  13.     sys.exit(0) 
  14.    
  15. if__name__=="__main__"
  16.   ApplicationInstance() 
  17.   whileTrue: 
  18.     print'running...' 
  19.     time.sleep(1) 

3、检测文件中PID

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importtime, os, sys 
  4. importsignal 
  5.    
  6. pidfile='/tmp/process.pid' 
  7.    
  8. defsig_handler(sig, frame): 
  9.   ifos.path.exists(pidfile): 
  10.     os.remove(pidfile) 
  11.   sys.exit(0) 
  12.    
  13. defApplicationInstance(): 
  14.   signal.signal(signal.SIGTERM, sig_handler) 
  15.   signal.signal(signal.SIGINT, sig_handler) 
  16.   signal.signal(signal.SIGQUIT, sig_handler) 
  17.    
  18.   try: 
  19.    pf=file(pidfile,'r'
  20.    pid=int(pf.read().strip()) 
  21.    pf.close() 
  22.   exceptIOError: 
  23.    pid=None 
  24.     
  25.   ifpid: 
  26.    sys.stdout.write('instance is running...\n'
  27.    sys.exit(0) 
  28.    
  29.   file(pidfile,'w+').write('%s\n'%os.getpid()) 
  30.    
  31. if__name__=="__main__"
  32.   ApplicationInstance() 
  33.   whileTrue: 
  34.     print'running...' 
  35.     time.sleep(1) 

4、检测特定文件夹或文件

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importtime, commands, signal, sys 
  4.    
  5. defsig_handler(sig, frame): 
  6.   ifos.path.exists("/tmp/test"): 
  7.     os.rmdir("/tmp/test"
  8.   sys.exit(0) 
  9.    
  10. defApplicationInstance(): 
  11.   signal.signal(signal.SIGTERM, sig_handler) 
  12.   signal.signal(signal.SIGINT, sig_handler) 
  13.   signal.signal(signal.SIGQUIT, sig_handler) 
  14.   ifcommands.getstatusoutput("mkdir /tmp/test")[0]: 
  15.     print"instance is running..." 
  16.     sys.exit(0) 
  17.    
  18. if__name__=="__main__"
  19.   ApplicationInstance() 
  20.   whileTrue: 
  21.     print'running...' 
  22.     time.sleep(1) 

也可以检测某一个特定的文件,判断文件是否存在:

  1. importos 
  2. importos.path 
  3. importtime 
  4.     
  5.     
  6. #class used to handle one application instance mechanism 
  7. classApplicationInstance: 
  8.     
  9.   #specify the file used to save the application instance pid 
  10.   def__init__(self, pid_file ): 
  11.     self.pid_file=pid_file 
  12.     self.check() 
  13.     self.startApplication() 
  14.     
  15.   #check if the current application is already running 
  16.   defcheck(self): 
  17.     #check if the pidfile exists 
  18.     ifnotos.path.isfile(self.pid_file ): 
  19.       return 
  20.     #read the pid from the file 
  21.     pid=0 
  22.     try: 
  23.       file=open(self.pid_file,'rt'
  24.       data=file.read() 
  25.       file.close() 
  26.       pid=int( data ) 
  27.     except: 
  28.       pass 
  29.     #check if the process with specified by pid exists 
  30.     if0==pid: 
  31.       return 
  32.     
  33.     try: 
  34.       os.kill( pid,0) #this will raise an exception if the pid is not valid 
  35.     except: 
  36.       return 
  37.     
  38.     #exit the application 
  39.     print"The application is already running..." 
  40.     exit(0)#exit raise an exception so don't put it in a try/except block 
  41.     
  42.   #called when the single instance starts to save it's pid 
  43.   defstartApplication(self): 
  44.     file=open(self.pid_file,'wt'
  45.     file.write(str( os.getpid() ) ) 
  46.     file.close() 
  47.     
  48.   #called when the single instance exit ( remove pid file ) 
  49.   defexitApplication(self): 
  50.     try: 
  51.       os.remove(self.pid_file ) 
  52.     except: 
  53.       pass 
  54.     
  55.     
  56. if__name__=='__main__'
  57.   #create application instance 
  58.   appInstance=ApplicationInstance('/tmp/myapp.pid'
  59.     
  60.   #do something here 
  61.   print"Start MyApp" 
  62.   time.sleep(5) #sleep 5 seconds 
  63.   print"End MyApp" 
  64.     
  65.   #remove pid file 
  66.   appInstance.exitApplication() 

上述os.kill( pid, 0 )用于检测一个为pid的进程是否还活着,若该pid的进程已经停止则抛出异常,若正在运行则不发送kill信号。

5、socket监听一个特定端口

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importsocket, time, sys 
  4.    
  5.    
  6. defApplicationInstance(): 
  7.   try:   
  8.     globals 
  9.     s=socket.socket() 
  10.     host=socket.gethostname() 
  11.     s.bind((host,60123)) 
  12.   except: 
  13.     print"instance is running..." 
  14.     sys.exit(0) 
  15.    
  16. if__name__=="__main__"
  17.   ApplicationInstance() 
  18.   whileTrue: 
  19.     print'running...' 
  20.     time.sleep(1) 

可以将该函数使用装饰器实现,便于重用(效果与上述相同):

  1. #!/usr/bin/env python 
  2. #coding: utf-8 
  3. importsocket, time, sys 
  4. importfunctools 
  5.    
  6. #使用装饰器实现 
  7. defApplicationInstance(func): 
  8.   @functools.wraps(func) 
  9.   deffun(*args,**kwargs): 
  10.     importsocket 
  11.     try: 
  12.       globals 
  13.       s=socket.socket() 
  14.       host=socket.gethostname() 
  15.       s.bind((host,60123)) 
  16.     except: 
  17.       print('already has an instance...'
  18.       returnNone 
  19.     returnfunc(*args,**kwargs) 
  20.   returnfun 
  21.    
  22. @ApplicationInstance 
  23. defmain(): 
  24.   whileTrue: 
  25.     print'running...' 
  26.     time.sleep(1) 
  27.    
  28. if__name__=="__main__"
  29.   main() 

四、总结

(1)守护进程和单脚本运行在实际应用中比较重要,方法也比较多,可选择合适的来进行修改,可以将它们做成一个单独的类或模板,然后子类化实现自定义。

(2)daemon监控进程自动恢复避免了nohup和&的使用,并配合shell脚本可以省去很多不定时启动挂掉服务器的麻烦。

Tags: Python 守护进程

分享到: