全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

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

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

一、简介

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

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

二、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) 

运行结果:

 它是当Daemon设计成一个模板,在其他文件中from daemon import Daemon,然后定义子类,重写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()没有被调用。

然后,再写个shell命令,加入开机启动服务,每隔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 

三、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被系统回收(若整个函数写在主函数中,则不需要定义成global)。

              

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、检测文件中PID

#!/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信号。

5、socket监听一个特定端口

#!/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() 

四、总结

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# python  # 进程守护脚本  # 守护进程  # 启动守护进程  # python 如何设置守护进程  # Python 创建守护进程的示例  # Python守护进程实现过程详解  # python并发编程多进程之守护进程原理解析  # python实现守护进程、守护线程、守护非守护并行  # python使用fork实现守护进程的方法  # Python如何实现守护进程的方法示例  # python daemon守护进程实现  # Python守护进程用法实例分析  # python实现的守护进程(Daemon)用法实例  # 如何编写python的daemon程序  # 组头  # 重定向  # 是否存在  # 子类  # 自定义  # 领进  # 创建一个  # 小编  # 锁住  # 自己的  # 加锁  # 还可以  # 不需要  # 是由  # 最重要  # 它是  # 可以通过  # 第二个  # 给大家  # 这样做 


相关文章: 如何快速搭建二级域名独立网站?  浅析上传头像示例及其注意事项  如何通过免费商城建站系统源码自定义网站主题与功能?  nginx修改上传文件大小限制的方法  建站中国必看指南:CMS建站系统+手机网站搭建核心技巧解析  非常酷的网站设计制作软件,酷培ai教育官方网站?  存储型VPS适合搭建中小型网站吗?  如何获取开源自助建站系统免费下载链接?  如何用低价快速搭建高质量网站?  陕西网站制作公司有哪些,陕西凌云电器有限公司官网?  建站之星如何实现五合一智能建站与营销推广?  如何在建站主机中优化服务器配置?  北京网页设计制作网站有哪些,继续教育自动播放怎么设置?  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  公众号网站制作网页,微信公众号怎么制作?  如何配置支付宝与微信支付功能?  如何快速上传建站程序避免常见错误?  如何零基础开发自助建站系统?完整教程解析  制作网站的软件免费下载,免费制作app哪个平台好?  建站之星如何配置系统实现高效建站?  如何在Windows服务器上快速搭建网站?  ui设计制作网站有哪些,手机UI设计网址吗?  文字头像制作网站推荐软件,醒图能自动配文字吗?  如何用腾讯建站主机快速创建免费网站?  如何在阿里云服务器自主搭建网站?  如何在Windows环境下新建FTP站点并设置权限?  青浦网站制作公司有哪些,苹果官网发货地是哪里?  建站主机与虚拟主机有何区别?如何选择最优方案?  linux top下的 minerd 木马清除方法  如何高效利用200m空间完成建站?  制作农业网站的软件,比较好的农业网站推荐一下?  建站主机服务器选购指南:轻量应用与VPS配置解析  微信网站制作公司有哪些,民生银行办理公司开户怎么在微信网页上查询进度?  太平洋网站制作公司,网络用语太平洋是什么意思?  如何解决VPS建站LNMP环境配置常见问题?  头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?  建站主机核心功能解析:服务器选择与网站搭建流程指南  如何在云虚拟主机上快速搭建个人网站?  制作公司内部网站有哪些,内网如何建网站?  家庭服务器如何搭建个人网站?  免费视频制作网站,更新又快又好的免费电影网站?  胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?  淘宝制作网站有哪些,淘宝网官网主页?  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  宝塔建站教程:一键部署配置流程与SEO优化实战指南  小米网站链接制作教程,请问miui新增网页链接调用服务有什么用啊?  官网自助建站系统:SEO优化+多语言支持,快速搭建专业网站  建站之星如何修改网站生成路径? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。