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

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

一、简介

     守护进程最重要的特性是后台运行;它必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等;它可以在系统启动时从启动脚本/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)  

运行结果:

       可以参考:http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/,它是当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脚本可以省去很多不定时启动挂掉服务器的麻烦。
(3)若有更好的设计和想法,可随时留言,在此先感谢!

转载于:https://my.oschina.net/mickelfeng/blog/968356

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/258394.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AD20学习笔记4---网表导入及模块化布局设计

前言: 本文学习视频是B站点击率第一的凡亿教育《Altium Designer 20 19(入门到精通全38集)四层板智能车PCB设计视频教程》,视频地址:Altium Designer 20 19(入门到精通全38集)四层板智能车PCB设…

javascript 模块化

2019独角兽企业重金招聘Python工程师标准>>> 一直好奇像node.js,require.js的模块化是怎么做的,在看了《你不知道的javascript》后,对js的模块化有了一些简单的了解。这本书真的还不错。 书里讲述了js的模块化的原理 和 现代js实现模块化的简…

AD20学习笔记5---PCB设计规则设置及PCB手工布线

前言: 本文学习视频是B站点击率第一的凡亿教育《Altium Designer 20 19(入门到精通全38集)四层板智能车PCB设计视频教程》,视频地址:Altium Designer 20 19(入门到精通全38集)四层板智能车PCB设…

理论物理极础9:相空间流体和吉布斯-刘维尔定理

莱尼喜欢看河,尤其喜欢看漂浮物顺流而下。他猜想漂浮物如何穿过礁石,如何陷入漩涡。但是河流整体,水量,流切变,河的分流和汇聚,这是莱尼所看不到的。 相空间流体 在经典力学里,注视一个特别的初…

nginx没有worker进程_如何优雅地关闭worker进程?

点击上方“武培轩”,选择“设为星标”技术文章第一时间送达!之前我们讲解 Nginx 命令行的时候,可以看到 Nginx 停止有两种方式,分别是 nginx -s quit 和 nginx -s stop,其中 stop 是指立即停止 Nginx,而 qu…

RC电路输出波形的时域与频域分析

RC一阶电路为例进行时域和频域分析,激励Us为方波,以Uc作为输出的波形相当于积分电路的输出曲线,以Ur作为输出的波形相当于微分电路的输出曲线。电容对输入电压具有平滑作用,平滑程度与时间常数有关,衰减程度与带宽设计…

多麦克风做拾音的波束_麦克风阵列是什么 有哪些关键技术?

麦克风阵列是什么 有哪些关键技术?亚马逊Echo和谷歌Home争奇斗艳,除了云端服务,他们在硬件上到底有哪些差异?我们先将Echo和Home两款音箱拆开来看,区别最大的还是麦克风阵列技术。Amazon Echo采用的是环形61麦克风阵列…

如何用AD20打开ddb文件

用AD20直接打开ddb文件会报错,在AD20中使用导入向导才是ddb文件的正确打开方式。 1.用AD20直接打开ddb文件的报错提示 2.使用导入向导打开ddb文件 除了以下两处关键设置的地方,一路next就行。

Codeforces Round #419 (Div. 2)

1.题目A:Karen and Morning 题意: 给出hh:mm格式的时间,问至少经过多少分钟后,该时刻为回文字符串? 思路: 简单模拟,从当前时刻开始,如果hh的回文rh等于mm则停止累计。否则&#xff…

Java NIO 系列教程

Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。本系列教程将有助于你学习和理解Java NIO。感谢并发编程网的翻译和投递。 (关注ITeye官微,随时随地查看最新开发资讯、技术文章…

Multisim14仿真入门笔记

本文是B站北京邮电大学邓刚老师《Multisim仿真入门》的学习笔记,视频地址:【电路仿真】Multisim仿真入门(北京邮电大学 邓刚主讲)_哔哩哔哩_bilibili。 1.Multisim简介 Multisim14是一种专门用于电路仿真和设计的软件之一&#x…

office365在win7上使用订阅+win7在线升级win10就用它(亲测有效)

前言: 作为office365的重度使用用户,最近两天订阅到期,续订之后一直显示无法验证此订阅(仅查看),office365未经授权,大多数功能已停用,狂晕!!! 在…

HDU 4414 Finding crosses(搜索)

题目链接:HDU 4414 Finding crosses 【题目大意】 给你一张n*n的图,由o #这两个元素组成,让我们找其中有多少十字架。 十字架由#构成 十字架的纵向长度等于横向长度 , 且这个长度要为大于等于3的奇数。 构成十字架的#周围不能有多…

递归和分治思想及其应用

目录 递归和分治思想一些实例逆序输出字符串查找数组元祖是否存在汉诺塔问题八皇后问题更多:递归和分治思想 如果可以使用迭代,尽量别使用递归。由编译原理可以知道,每次自调用的时候,计算机都需要保存在调用,浪费时间…

AM+PM+FM基本调制原理及相关理论

总论: 调制信号: 模拟信号m(t),可以是正弦波信号、方波信号等任意信号,又称基带信号 载波信号:一般为正弦波信号 已调信号: 幅度调制AM---A(t)随m(t)成比例变化----线性调制 相位调制PM---随m(t)成比…

win32 api 文件操作!

CreateFile打开文件要对文件进行读写等操作,首先必须获得文件句柄,通过该函数可以获得文件句柄,该函数是通向文件世界的大门。ReadFile从文件中读取字节信息。在打开文件获得了文件句柄之后,则可以通过该函数读取数据。WriteFile向…

小说里的lt什么意思_游戏cpdd网络用语是什么意思 王者荣耀里很常见

[闽南网]随着互联网的发展,越来越多的流行语横空出世,在网络上得到广泛使用。当一个网络语流行的时候,不管在微博上还是贴吧里,都会看见和流行语有关的句子和表情包。眼下在各种游戏里,总是能看到游戏玩家们说“cpdd”…

linux系统 硬链接和软链接

背景: 当几个用户同在一个项目里工作时。经常须要共享文件。假设一个共享文件同一时候出如今属于不同用户的不同文件夹下。工作起来就非常方便。比如B和C文件夹下有一文件D是两者都能够訪问和改动的共享文件,这样是非常方便,但也会有一些问题…

openssl 学习之从证书中提取RSA公钥N 和 E

原文链接: http://blog.csdn.net/kkxgx/article/details/19850509 通常数字证书包含很多信息,其中N和E值即我们称为的公钥。如何从PEM 或者DER格式的证书中提出证书呢?下面给出代码实现从PEM和DER编码的证书中提出N、E。 [cpp] view plaincopy #include …

Web browser的发展演变

我们每天都在使用着浏览器,每个人使用的浏览器各不一样。在这个科技飞速发展的时代,一个游览器能否站住脚跟取决于使用者的数量,看用户是否喜欢这个产品,听取用户们的意见来改善。 我们这个年龄的人最初用到的浏览器肯定是IE浏览器…