侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

python使用fork实现守护进程的方法

Python  /  管理员 发布于 7年前   196

os模块中的fork方法可以创建一个子进程。相当于克隆了父进程

os.fork()

子进程运行时,os.fork方法会返回0;

 而父进程运行时,os.fork方法会返回子进程的PID号。

所以可以使用PID来区分两个进程:

 #!/usr/bin/env python #coding=utf8  from time import sleep import os  try: pid = os.fork() except OSError, e: pass  sleep(30)

运行代码,查看进程:

[root@localhost ~]# python test2.py &[1] 2464[root@localhost ~]# ps -lF S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD4 S 0 2379 2377 0 80 0 - 28879 wait pts/1 00:00:00 bash0 S 0 2464 2379 0 80 0 - 31318 poll_s pts/1 00:00:00 python1 S 0 2465 2464 0 80 0 - 31318 poll_s pts/1 00:00:00 python0 R 0 2466 2379 0 80 0 - 37227 - pts/1 00:00:00 ps​

可以看出第二条python进程就是第一条的子进程。

如刚刚所说os.fork()方法区分子进程和父进程

#-*- coding:utf-8 -*- from time import sleepimport os print('start+++++++++++++')#创建子进程之前声明的变量source = 10try:    pid = os.fork()    print('pid=',pid)    if pid == 0: #子进程        print("this is child process.")        source = source - 1 #在子进程中source减1    else: #父进程        print("this is parent process." )    print(source)except (OSError,e):    passprint('END---------------')  

面代码中,在子进程创建前,声明了一个变量source,然后在子进程中减1,最后打印出source的值,显然父进程打印出来的值应该为10,子进程打印出来的值应该为9。

[root@localhost ~]# python test3.pystart+++++++++++++pid= 2550this is parent process.10END---------------pid= 0this is child process.9END---------------​  

 简单守护进程例子:

def main():  ''' 程序要执行的逻辑代码 '''  pass  # 创建守护进程函数def createDaemon():  ''' 第一块(创建第一个子进程) '''  # fork 第一个子进程(如果fork成功,父进程自杀,只留下第一个子进程继续向下运行)  try:    if os.fork() > 0:      sys.exit(0)  except OSError, error:    print '(fork第一个子进程失败)fork #1 failed: %d (%s)' % (error.errno, error.strerror)    sys.exit(1)  ''' 第一块结束 '''   ###### 第一个进程创建成功后,它的ppid = 1,已是一个守护里程了,但有些功能上还是有被限制。  ###### 所以下面再来创建一个子进程。第二次创建的子进程限制就没那多了,有可能没有,所以最安全。  ###### 下面来创建第二个子进程。     os.chdir('/') # 把第一个子进程的工作目录切换到 / (根目录)  os.setsid() # 第一个子进程取得程序的权限  os.umask(0) # 第一个子进程取得工作目录的所有操作(目录的rwx)    ''' 第二块(创建第二个子进程) '''  # fork 第二个子进程(如果fork成功,第一个子进程自杀,只留下新创建的第二个子进程)  try:    pid = os.fork()    if pid > 0:      print 'Daemon PID %d' % pid      sys.exit(0)  except OSError, error:    print '(fork第二个子进程失败)fork #2 failed: %d (%s)' % (error.errno, error.strerror)    sys.exit(1)  ''' 第二块结束 '''    ####### 通过上面两个 try 语句块,只留下了第二个子进程在运行了。这时第二个子进程的ppid=1。  ####### 创建的第二个子进程,可以说是一个不受限的守护进程了。     # 重定向标准IO(因为只有第二个子进程在运行了,所以也就是指定整个程序的输入、输出、错误流)     # sys.stdout.flush() # 清除程序运行空间的输出流  # sys.stderr.flush() # 清除程序运行空间的错误流   # inputS = file("/dev/null", 'r')  # 定义一个 inputS 文件对象  # outputS = file("/dev/null", 'a+') # 定义一个 outputS 文件对象  # errorS = file("/dev/null", 'a+', 0) # 定义一个 errorS 文件对象   # os.dup2(inputS.fileno(), sys.stdin.fileno()) # 把程序的输入流重定向到上面定义的 inputS 文件对象上。  # os.dup2(so.fileno(), sys.stdout.fileno()) # 把程序的 输出流 重定向到上面定义的 outputS 文件对象上。  # os.dup2(se.fileno(), sys.stderr.fileno()) # 把程序的 错误流 重定向到上面定义的 errorS 文件对象上。   main() # main函数为真正程序逻辑代码  if __name__ == "__main__":  if platform.system() == "Linux":      createDaemon()    else:      sys.exit()    

 带控制参数的例子:

编写守护进程的基类,用于继承:

# coding: utf-8 import osimport sysimport timeimport atexitimport signal  class Daemon:  def __init__(self, pidfile='/tmp/daemon.pid', stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):    self.stdin = stdin    self.stdout = stdout    self.stderr = stderr    self.pidfile = pidfile   def daemonize(self):    if os.path.exists(self.pidfile):      raise RuntimeError('Already running.')     # First fork (detaches from parent)    try:      if os.fork() > 0:        raise SystemExit(0)    except OSError as e:      raise RuntimeError('fork #1 faild: {0} ({1})\n'.format(e.errno, e.strerror))     os.chdir('/')    os.setsid()    os.umask(0o22)     # Second fork (relinquish session leadership)    try:      if os.fork() > 0:        raise SystemExit(0)    except OSError as e:      raise RuntimeError('fork #2 faild: {0} ({1})\n'.format(e.errno, e.strerror))     # Flush I/O buffers    sys.stdout.flush()    sys.stderr.flush()     # Replace file descriptors for stdin, stdout, and stderr    with open(self.stdin, 'rb', 0) as f:      os.dup2(f.fileno(), sys.stdin.fileno())    with open(self.stdout, 'ab', 0) as f:      os.dup2(f.fileno(), sys.stdout.fileno())    with open(self.stderr, 'ab', 0) as f:      os.dup2(f.fileno(), sys.stderr.fileno())     # Write the PID file    with open(self.pidfile, 'w') as f:      print(os.getpid(), file=f)     # Arrange to have the PID file removed on exit/signal    atexit.register(lambda: os.remove(self.pidfile))     signal.signal(signal.SIGTERM, self.__sigterm_handler)   # Signal handler for termination (required)  @staticmethod  def __sigterm_handler(signo, frame):    raise SystemExit(1)   def start(self):    try:      self.daemonize()    except RuntimeError as e:      print(e, file=sys.stderr)      raise SystemExit(1)     self.run()   def stop(self):    try:      if os.path.exists(self.pidfile):        with open(self.pidfile) as f:          os.kill(int(f.read()), signal.SIGTERM)      else:        print('Not running.', file=sys.stderr)        raise SystemExit(1)    except OSError as e:      if 'No such process' in str(e) and os.path.exists(self.pidfile):        os.remove(self.pidfile)   def restart(self):    self.stop()    self.start()   def run(self):  #继承类重写该方法    pass

  编写自己的类:

 #导入刚刚编写的基类 from daemon import Daemon #继承 class MyTestDaemon(Daemon): #重写run方法,就是你要后台运行的函数   def run(self):   #后台运行的函数,比如shell输出到自己定义的文件     sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid()))     while True:       sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime()))       sys.stdout.flush()        time.sleep(5)  if __name__ == '__main__':   PIDFILE = '/tmp/daemon-example.pid'   LOG = '/tmp/daemon-example.log'   daemon = MyTestDaemon(pidfile=PIDFILE, stdout=LOG, stderr=LOG)    if len(sys.argv) != 2:     print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr)     raise SystemExit(1)    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 {!r}'.format(sys.argv[1]), file=sys.stderr)     raise SystemExit(1)

关于两次fork

第二个fork不是必须的,只是为了防止进程打开控制终端。

打开一个控制终端的条件是该进程必须是session leader。第一次fork,setsid之后,子进程成为session leader,进程可以打开终端;第二次fork产生的进程,不再是session leader,进程则无法打开终端。

也就是说,只要程序实现得好,控制程序不主动打开终端,无第二次fork亦可。

代码实现

# coding: utf-8import osimport sysimport timeimport atexitimport signalclass Daemon:  def __init__(self, pidfile='/tmp/daemon.pid', stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):    self.stdin = stdin    self.stdout = stdout    self.stderr = stderr    self.pidfile = pidfile  def daemonize(self):    if os.path.exists(self.pidfile):      raise RuntimeError('Already running.')    # First fork (detaches from parent)    try:      if os.fork() > 0:        raise SystemExit(0)    except OSError as e:      raise RuntimeError('fork #1 faild: {0} ({1})\n'.format(e.errno, e.strerror))    os.chdir('/')    os.setsid()    os.umask(0o22)    # Second fork (relinquish session leadership)    try:      if os.fork() > 0:        raise SystemExit(0)    except OSError as e:      raise RuntimeError('fork #2 faild: {0} ({1})\n'.format(e.errno, e.strerror))    # Flush I/O buffers    sys.stdout.flush()    sys.stderr.flush()    # Replace file descriptors for stdin, stdout, and stderr    with open(self.stdin, 'rb', 0) as f:      os.dup2(f.fileno(), sys.stdin.fileno())    with open(self.stdout, 'ab', 0) as f:      os.dup2(f.fileno(), sys.stdout.fileno())    with open(self.stderr, 'ab', 0) as f:      os.dup2(f.fileno(), sys.stderr.fileno())    # Write the PID file    with open(self.pidfile, 'w') as f:      print(os.getpid(), file=f)    # Arrange to have the PID file removed on exit/signal    atexit.register(lambda: os.remove(self.pidfile))    signal.signal(signal.SIGTERM, self.__sigterm_handler)  # Signal handler for termination (required)  @staticmethod  def __sigterm_handler(signo, frame):    raise SystemExit(1)  def start(self):    try:      self.daemonize()    except RuntimeError as e:      print(e, file=sys.stderr)      raise SystemExit(1)    self.run()  def stop(self):    try:      if os.path.exists(self.pidfile):        with open(self.pidfile) as f:          os.kill(int(f.read()), signal.SIGTERM)      else:        print('Not running.', file=sys.stderr)        raise SystemExit(1)    except OSError as e:      if 'No such process' in str(e) and os.path.exists(self.pidfile):         os.remove(self.pidfile)  def restart(self):    self.stop()    self.start()  def run(self):    pass

使用测试

import osimport sysimport timefrom daemon import Daemonclass MyTestDaemon(Daemon):  def run(self):    sys.stdout.write('Daemon started with pid {}\n'.format(os.getpid()))    while True:      sys.stdout.write('Daemon Alive! {}\n'.format(time.ctime()))      sys.stdout.flush()      time.sleep(5)if __name__ == '__main__':  PIDFILE = '/tmp/daemon-example.pid'  LOG = '/tmp/daemon-example.log'  daemon = MyTestDaemon(pidfile=PIDFILE, stdout=LOG, stderr=LOG)  if len(sys.argv) != 2:    print('Usage: {} [start|stop]'.format(sys.argv[0]), file=sys.stderr)    raise SystemExit(1)  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 {!r}'.format(sys.argv[1]), file=sys.stderr)    raise SystemExit(1)
[daemon] python test.py start         23:45:42[daemon] cat /tmp/daemon-example.pid     23:45:498532[daemon] ps -ef|grep 8532 | grep -v grep   23:46:07 502 8532   1  0 11:45下午 ??     0:00.00 python test.py start[daemon] tail -f /tmp/daemon-example.log   23:46:20Daemon started with pid 8532Daemon Alive! Fri Dec 2 23:45:49 2016Daemon Alive! Fri Dec 2 23:45:54 2016Daemon Alive! Fri Dec 2 23:45:59 2016Daemon Alive! Fri Dec 2 23:46:04 2016Daemon Alive! Fri Dec 2 23:46:09 2016Daemon Alive! Fri Dec 2 23:46:14 2016Daemon Alive! Fri Dec 2 23:46:19 2016Daemon Alive! Fri Dec 2 23:46:24 2016Daemon Alive! Fri Dec 2 23:46:29 2016Daemon Alive! Fri Dec 2 23:46:34 2016[daemon] python test.py stop         23:46:36[daemon] ps -ef|grep 8532 | grep -v grep   23:46:43


  • 上一条:
    高质量Python代码编写的5个优化技巧
    下一条:
    详解Python map函数及Python map()函数的用法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客