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

作者:zimsan 时间:2021-08-27 00:37:50 

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

来源:http://www.cnblogs.com/zimsan/p/7840306.html

标签:python,守护进程
0
投稿

猜你喜欢

  • 条件注释使用指南[译]

    2009-03-23 17:41:00
  • 在Python中调用ggplot的三种方法

    2023-08-23 00:40:58
  • 微信小程序时间轴实现方法示例

    2024-04-18 10:02:17
  • python3 kubernetes api的使用示例

    2021-11-11 00:56:18
  • 遗传算法python版

    2021-04-29 23:36:20
  • 友情连接地址代码-线线表格

    2010-07-01 16:26:00
  • Go泛型实战教程之如何在结构体中使用泛型

    2024-05-13 10:45:00
  • python列表数据增加和删除的具体实例

    2021-08-05 15:11:09
  • js绘制购物车抛物线动画

    2024-04-10 16:18:09
  • 一文了解Python并发编程的工程实现方法

    2023-12-26 13:54:36
  • Mongoose经常返回e11000 error的原因分析

    2024-05-03 15:36:05
  • SQL Server数据库内存会不断增加的问题分析

    2009-01-08 15:46:00
  • 利用python实现平稳时间序列的建模方式

    2022-08-31 03:35:30
  • 跨浏览器的本地存储(二):DOM:Storage

    2008-08-15 13:39:00
  • Python-嵌套列表list的全面解析

    2022-11-08 23:33:08
  • python对矩阵进行转置的2种处理方法

    2023-12-01 09:18:17
  • Python 网页请求之requests库的使用详解

    2021-01-30 23:42:06
  • 基于Python实现有趣的象棋游戏

    2022-06-11 20:37:15
  • 解析优化MySQL插入方法的五个妙招

    2024-01-19 13:13:49
  • python使用wxpy实现微信消息防撤回脚本

    2023-08-22 21:21:58
  • asp之家 网络编程 m.aspxhome.com