Python 创建守护进程的示例


Posted in Python onSeptember 29, 2020

考虑如下场景:你编写了一个python服务程序,并且在命令行下启动,而你的命令行会话又被终端所控制,python服务成了终端程序的一个子进程。因此如果你关闭了终端,这个命令行程序也会随之关闭。
要使你的python服务不受终端影响而常驻系统,就需要将它变成守护进程。
守护进程就是Daemon程序,是一种在系统后台执行的程序,它独立于控制终端并且执行一些周期任务或触发事件,通常被命名为"d"字母结尾,如常见的httpd、syslogd、systemd和dockerd等。

代码实现

python可以很简洁地实现守护进程,下面先给出代码和相应注释:

# coding=utf8
import os
import sys
import atexit
 
 
def daemonize(pid_file=None):
  """
  创建守护进程
  :param pid_file: 保存进程id的文件
  :return:
  """
  # 从父进程fork一个子进程出来
  pid = os.fork()
  # 子进程的pid一定为0,父进程大于0
  if pid:
    # 退出父进程,sys.exit()方法比os._exit()方法会多执行一些刷新缓冲工作
    sys.exit(0)
 
  # 子进程默认继承父进程的工作目录,最好是变更到根目录,否则回影响文件系统的卸载
  os.chdir('/')
  # 子进程默认继承父进程的umask(文件权限掩码),重设为0(完全控制),以免影响程序读写文件
  os.umask(0)
  # 让子进程成为新的会话组长和进程组长
  os.setsid()
 
  # 注意了,这里是第2次fork,也就是子进程的子进程,我们把它叫为孙子进程
  _pid = os.fork()
  if _pid:
    # 退出子进程
    sys.exit(0)
 
  # 此时,孙子进程已经是守护进程了,接下来重定向标准输入、输出、错误的描述符(是重定向而不是关闭, 这样可以避免程序在 print 的时候出错)
 
  # 刷新缓冲区先,小心使得万年船
  sys.stdout.flush()
  sys.stderr.flush()
 
  # dup2函数原子化地关闭和复制文件描述符,重定向到/dev/nul,即丢弃所有输入输出
  with open('/dev/null') as read_null, open('/dev/null', 'w') as write_null:
    os.dup2(read_null.fileno(), sys.stdin.fileno())
    os.dup2(write_null.fileno(), sys.stdout.fileno())
    os.dup2(write_null.fileno(), sys.stderr.fileno())
 
  # 写入pid文件
  if pid_file:
    with open(pid_file, 'w+') as f:
      f.write(str(os.getpid()))
    # 注册退出函数,进程异常退出时移除pid文件
    atexit.register(os.remove, pid_file)

概括一下守护进程的编写步骤:

  1. fork出子进程,退出父进程
  2. 子进程变更工作目录(chdir)、文件权限掩码(umask)、进程组和会话组(setsid)
  3. 子进程fork孙子进程,退出子进程
  4. 孙子进程刷新缓冲,重定向标准输入/输出/错误(一般到/dev/null,意即丢弃)
  5. (可选)pid写入文件

理解几个要点

为什么要fork两次

第一次fork,是为了脱离终端控制的魔爪。父进程之所以退出,是因为终端敲击键盘、或者关闭时给它发送了信号;而fork出来的子进程,在父进程自杀后成为孤儿进程,进而被操作系统的init进程接管,因此脱离终端控制。
所以其实,第二次fork并不是必须的(很多开源项目里的代码就没有fork两次)。只不过出于谨慎考虑,防止进程再次打开一个控制终端。因为子进程现在是会话组长了(对话期的首次进程),有能力打开控制终端,再fork一次,孙子进程就不能打开控制终端了。

文件描述符

Linux是“一切皆文件”,文件描述符是内核为已打开的文件所创建的索引,通常是非负整数。进程通过文件描述符执行IO操作。
默认情况下,0代表标准输入,1代表标准输出,2代表标准错误。

umask权限掩码

我们知道,在Linux中,任何一个文件都有读(read)、写(write)和执行(execute)的三种使用权限。其中,读的权限用数字4代表,写权限是2,执行权限是1。命令ls -l可以查看文件权限,r/w/x分别表示具有读/写/执行权限。
任何文件,也都有用户(User),用户组(Group),其他组(Others)三种身份权限。一般用3个数字表示文件权限,例如754:

  • 7,是User权限,即文件拥有者权限
  • 5,是Group权限,拥有者所在用户组的组员所具有的权限
  • 4,是Others权限,即其他组用户的权限啦

而umask是为了控制默认权限,防止新建文件或文件夹具有全权。
系统一般默认为022(使用命令umask查看),表示默认创建文件的权限是644,文件夹是755。你应该可以看出它们的规律,就是文件权限和umask的相加结果为666(笑),文件夹权限和umask的相加结果为777。

进程组

每个进程都属于一个进程组(PG,Process Group),进程组可以包含多个进程。
进程组有一个进程组长(Leader),进程组长的ID(PID, Process ID)就作为整个进程组的ID(PGID,Process Groupd ID)。

会话组

登陆终端时,就会创造一个会话,多个进程组可以包含在一个会话中。而创建会话的进程,就是会话组长。
已经是会话组长的进程,不可以再调用setsid()方法创建会话。因此,上面代码中,子进程可以调用setsid(),而父进程不能,因为它本身就是会话组长。
另外,sh(Bourne Shell)不支持会话机制,因为会话机制需要shell支持工作控制(Job Control)。

守护进程与后台进程

通过&符号,可以把命令放到后台执行。它与守护进程是不同的:

  1. 守护进程与终端无关,是被init进程收养的孤儿进程;而后台进程的父进程是终端,仍然可以在终端打印
  2. 守护进程在关闭终端时依然坚挺;而后台进程会随用户退出而停止,除非加上nohup
  3. 守护进程改变了会话、进程组、工作目录和文件描述符,后台进程直接继承父进程(shell)的

换句话说:守护进程就是默默地奋斗打拼的有为青年,而后台进程是默默继承老爸资产的富二代。

以上就是Python 创建守护进程的示例的详细内容,更多关于Python 创建守护进程的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python清除字符串里非字母字符的方法
Jul 02 Python
Python中操作符重载用法分析
Apr 29 Python
Python使用re模块实现信息筛选的方法
Apr 29 Python
Anaconda2下实现Python2.7和Python3.5的共存方法
Jun 11 Python
详解Python requests 超时和重试的方法
Dec 18 Python
python使用response.read()接收json数据的实例
Dec 19 Python
PyQt4编程之让状态栏显示信息的方法
Jun 18 Python
详解python中的模块及包导入
Aug 30 Python
基于python3生成标签云代码解析
Feb 18 Python
浅谈keras 模型用于预测时的注意事项
Jun 27 Python
Python文件操作及内置函数flush原理解析
Oct 13 Python
如何将anaconda安装配置的mmdetection环境离线拷贝到另一台电脑
Oct 15 Python
Python 解析xml文件的示例
Sep 29 #Python
Python 字典一个键对应多个值的方法
Sep 29 #Python
python 获取字典特定值对应的键的实现
Sep 29 #Python
Python3 pyecharts生成Html文件柱状图及折线图代码实例
Sep 29 #Python
Python爬取微信小程序通用方法代码实例详解
Sep 29 #Python
详解如何修改python中字典的键和值
Sep 29 #Python
提高python代码运行效率的一些建议
Sep 29 #Python
You might like
PHP 导出数据到淘宝助手CSV的方法分享
2010/02/27 PHP
PHP之浮点数计算比较以及取整数不准确的解决办法
2015/07/29 PHP
jquery 插件实现图片延迟加载效果代码
2010/02/06 Javascript
JavaScript之引用类型介绍
2012/08/10 Javascript
基于JQuery模仿苹果桌面的Dock效果(初级版)
2012/10/15 Javascript
关于extjs4如何获取grid修改后的数据的问题
2013/08/07 Javascript
javascript数组去重方法汇总
2015/04/23 Javascript
js基于面向对象实现网页TAB选项卡菜单效果代码
2015/09/09 Javascript
jQuery实现MSN中文网滑动Tab菜单效果代码
2015/09/09 Javascript
jQuery添加和删除输入文本框标签代码
2016/05/20 Javascript
javascript之with的使用(阿里云、淘宝使用代码分析)
2016/10/11 Javascript
AjaxUpLoad.js实现文件上传功能
2018/03/02 Javascript
ES6 系列之 WeakMap的使用示例
2018/08/06 Javascript
js实现随机div颜色位置 类似满天星效果
2019/10/24 Javascript
vue+elementui实现点击table中的单元格触发事件--弹框
2020/07/18 Javascript
Python Deque 模块使用详解
2014/07/04 Python
Python实现冒泡,插入,选择排序简单实例
2014/08/18 Python
Python3中的2to3转换工具使用示例
2015/06/12 Python
Python实现字典去除重复的方法示例
2017/07/31 Python
Python编程之Re模块下的函数介绍
2017/10/28 Python
numpy判断数值类型、过滤出数值型数据的方法
2018/06/09 Python
Python数学形态学实例分析
2019/09/06 Python
jupyter notebook 实现matplotlib图动态刷新
2020/04/22 Python
Django 构建模板form表单的两种方法
2020/06/14 Python
html5的画布canvas——画出弧线、旋转的图形实例代码+效果图
2013/06/09 HTML / CSS
html5 figure和figcaption的使用方法
2018/09/10 HTML / CSS
加拿大健康、婴儿和美容产品在线购物:Well.ca
2016/11/30 全球购物
中学生寄语大全
2014/04/03 职场文书
应届生找工作求职信
2014/06/24 职场文书
关于感恩的演讲稿500字
2014/08/26 职场文书
《爱的教育》读书心得
2014/11/08 职场文书
道歉信范文
2015/05/12 职场文书
2015年公路路政个人工作总结
2015/07/24 职场文书
Windows 11上手初体验:任务栏和开始菜单等迎来大改
2021/11/21 数码科技
Win10鼠标轨迹怎么开 Win10显示鼠标轨迹方法
2022/04/06 数码科技
Tomcat弱口令复现及利用
2022/05/06 Servers