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


Posted in Python onNovember 16, 2017

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 -l

F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD

4 S 0 2379 2377 0 80 0 - 28879 wait pts/1 00:00:00 bash

0 S 0 2464 2379 0 80 0 - 31318 poll_s pts/1 00:00:00 python

1 S 0 2465 2464 0 80 0 - 31318 poll_s pts/1 00:00:00 python

0 R 0 2466 2379 0 80 0 - 37227 - pts/1 00:00:00 ps​

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

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

#-*- coding:utf-8 -*-
 
from time import sleep
import os
 
print('start+++++++++++++')
#创建子进程之前声明的变量
source = 10
try:
    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):
    pass
print('END---------------')

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

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

 简单守护进程例子:

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 os
import sys
import time
import atexit
import 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-8

import os
import sys
import time
import atexit
import 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

使用测试

import os
import sys
import time

from daemon import Daemon

class 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:49
8532
[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:20
Daemon started with pid 8532
Daemon Alive! Fri Dec 2 23:45:49 2016
Daemon Alive! Fri Dec 2 23:45:54 2016
Daemon Alive! Fri Dec 2 23:45:59 2016
Daemon Alive! Fri Dec 2 23:46:04 2016
Daemon Alive! Fri Dec 2 23:46:09 2016
Daemon Alive! Fri Dec 2 23:46:14 2016
Daemon Alive! Fri Dec 2 23:46:19 2016
Daemon Alive! Fri Dec 2 23:46:24 2016
Daemon Alive! Fri Dec 2 23:46:29 2016
Daemon 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 相关文章推荐
python基于mysql实现的简单队列以及跨进程锁实例详解
Jul 07 Python
EM算法的python实现的方法步骤
Jan 02 Python
Python tkinter实现的图片移动碰撞动画效果【附源码下载】
Jan 04 Python
python使用Turtle库绘制动态钟表
Nov 19 Python
浅析python的优势和不足之处
Nov 20 Python
python傅里叶变换FFT绘制频谱图
Jul 19 Python
Python 装饰器原理、定义与用法详解
Dec 07 Python
pytorch torch.nn.AdaptiveAvgPool2d()自适应平均池化函数详解
Jan 03 Python
Python实现新型冠状病毒传播模型及预测代码实例
Feb 05 Python
Django 再谈一谈json序列化
Mar 16 Python
基于python实现监听Rabbitmq系统日志代码示例
Nov 28 Python
python FTP编程基础入门
Feb 27 Python
详解Python map函数及Python map()函数的用法
Nov 16 #Python
python中lambda()的用法
Nov 16 #Python
Python reduce()函数的用法小结
Nov 15 #Python
python简单实例训练(21~30)
Nov 15 #Python
python下10个简单实例代码
Nov 15 #Python
python获取多线程及子线程的返回值
Nov 15 #Python
python使用threading获取线程函数返回值的实现方法
Nov 15 #Python
You might like
锁定年轻人的双倍活力 星巴克推出星倍醇即饮浓咖啡
2021/03/03 咖啡文化
PHP+AJAX实现无刷新注册(带用户名实时检测)
2006/12/02 PHP
如何获知PHP程序占用多少内存(memory_get_usage)
2012/09/23 PHP
详解关于php的xdebug配置(编辑器vscode)
2019/01/29 PHP
php文件后缀不强制为.php的实操方法
2019/09/18 PHP
javascript函数中的arguments参数
2010/08/01 Javascript
超轻量级的基于jquery的三级展开列表
2011/04/26 Javascript
JavaScript中的几个关键概念的理解-原型链的构建
2011/05/12 Javascript
extjs tabpanel限制选项卡数量实现思路及代码
2013/04/02 Javascript
JS实现点击下载的小例子
2013/07/10 Javascript
基于pthread_create,readlink,getpid等函数的学习与总结
2013/07/17 Javascript
javascript实现可拖动变色并关闭层窗口实例
2015/05/15 Javascript
基于jQuery实现左右图片轮播(原理通用)
2015/12/24 Javascript
基于JS模仿windows文件按名称排序效果
2016/06/29 Javascript
JavaScript实现Java中Map容器的方法
2016/10/09 Javascript
Node.js利用debug模块打印出调试日志的方法
2017/04/25 Javascript
VUE 3D轮播图封装实现方法
2018/07/03 Javascript
你应该了解的JavaScript Array.map()五种用途小结
2018/11/14 Javascript
基于vue的tab-list类目切换商品列表组件的示例代码
2020/02/14 Javascript
python实现获取Ip归属地等信息
2016/08/27 Python
如何定义TensorFlow输入节点
2020/01/23 Python
pycharm安装及如何导入numpy
2020/04/03 Python
利用OpenCV中对图像数据进行64F和8U转换的方式
2020/06/03 Python
Python faker生成器生成虚拟数据代码实例
2020/07/20 Python
如何用python免费看美剧
2020/08/11 Python
美国益智玩具购物网站:Fat Brain Toys
2017/11/03 全球购物
企划专员岗位职责
2013/12/09 职场文书
《锄禾》教学反思
2014/04/08 职场文书
财务会计大学生自我评价
2014/04/09 职场文书
苏州园林导游词
2015/02/03 职场文书
严以修身专题学习研讨会发言材料
2015/11/09 职场文书
2016年优秀共产党员先进事迹材料
2016/02/29 职场文书
七年级作文之我的梦想
2019/10/16 职场文书
python 如何将两个实数矩阵合并为一个复数矩阵
2021/05/19 Python
pytorch 如何使用amp进行混合精度训练
2021/05/24 Python
【海涛七七解说】DCG第二周:DK VS 天禄
2022/04/01 DOTA