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脚本处理空格的方法
Aug 08 Python
Python中str.format()详解
Mar 12 Python
Python编程之微信推送模板消息功能示例
Aug 21 Python
python3.6+opencv3.4实现鼠标交互查看图片像素
Feb 26 Python
python实现聊天小程序
Mar 13 Python
Python在groupby分组后提取指定位置记录方法
Apr 20 Python
对python 自定义协议的方法详解
Feb 13 Python
Python 转换RGB颜色值的示例代码
Oct 13 Python
PyTorch中的Variable变量详解
Jan 07 Python
Python编程快速上手——PDF文件操作案例分析
Feb 28 Python
基于python 等频分箱qcut问题的解决
Mar 03 Python
python2.7使用scapy发送syn实例
May 05 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
php中关于普通表单多文件上传的处理方法
2011/03/25 PHP
基于PHP读取TXT文件向数据库导入海量数据的方法
2013/04/23 PHP
php批量修改表结构实例
2017/05/24 PHP
PHP实现 APP端微信支付功能
2018/06/22 PHP
Laravel框架下载,安装及路由操作图文详解
2019/12/04 PHP
js操作iframe兼容各种主流浏览器示例代码
2013/07/22 Javascript
jQuery实现当按下回车键时绑定点击事件
2014/01/28 Javascript
javascript实现的一个随机点名功能
2014/08/26 Javascript
javascript实现全角与半角字符的转换
2015/01/07 Javascript
jQuery带时间的日期控件代码分享
2015/08/26 Javascript
JavaScript实现的多种鼠标拖放效果
2015/11/03 Javascript
js跨域请求数据的3种常用的方法
2015/12/01 Javascript
JS中Eval解析JSON字符串的一个小问题
2016/02/21 Javascript
javascript随机抽取0-100之间不重复的10个数
2016/02/25 Javascript
一波JavaScript日期判断脚本分享
2016/03/06 Javascript
使用JQuery 加载页面时调用JS的实现方法
2016/05/30 Javascript
手机Web APP如何实现分享多平台功能
2016/08/19 Javascript
JavaScript 冒泡排序和选择排序的实现代码
2016/09/03 Javascript
ionic选择多张图片上传的示例代码
2017/10/10 Javascript
vue项目中使用vue-i18n报错的解决方法
2019/01/13 Javascript
ES6中的类(Class)示例详解
2020/12/09 Javascript
Python实现随机生成有效手机号码及身份证功能示例
2017/06/05 Python
Python自定义线程池实现方法分析
2018/02/07 Python
Python3.4解释器用法简单示例
2019/03/22 Python
Python实现的栈、队列、文件目录遍历操作示例
2019/05/06 Python
使用Python将字符串转换为格式化的日期时间字符串
2019/09/01 Python
python录音并调用百度语音识别接口的示例
2020/12/01 Python
HTML5离线缓存Manifest是什么
2016/03/09 HTML / CSS
android面试问题与答案
2016/12/27 面试题
大学毕业感言一句话
2014/02/06 职场文书
《秋姑娘的信》教学反思
2014/02/28 职场文书
财务部会计岗位职责
2015/02/03 职场文书
建筑工地资料员岗位职责
2015/04/13 职场文书
2015年政协委员工作总结
2015/05/20 职场文书
MySQL 视图(View)原理解析
2021/05/19 MySQL
SQL使用复合索引实现数据库查询的优化
2022/05/25 SQL Server