python多进程——fork()
简介
程序每次执行时,操作系统都会创建一个新进程来运行程序指令。进程中可调用os.fork,要求操作系统新建一个子进程.[Windowsc系统中,os模块没有os.fork函数]。
每个进程都有一个不重复的进程ID号。或称pid,它对进程进行标识。子进程与父进程完全相同,子进程从父进程继承了多个值的拷贝。如全局变量和环境变量。fork后,子进程接收返回值0,而父进程接收子进程的pid作为返回值
os.
fork
()
Fork a child process. Return 0
in the child and the child’s process id in the parent. If an error occurs OSError is raised.
Note that some platforms including FreeBSD <= 6.3 and Cygwin have known issues when using fork() from a thread.
Availability: Unix > 仅支持基于Unix核心的系统
(一)fork函数在子进程中返回0,在父进程中返回子进程的id:
-
os.getpid() 返回进程pid
-
os.getppid() 返回父进程pid
# -*-coding:utf-8-*- import os import time print('before calling') p = os.fork() # 主进程,子进程同时向下执行 print('after calling') if p == 0: print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
[root@192 ~]# python fork.py
before calling
after calling
执行主进程, pid=1629 ppid=1572 p=1630
after calling
执行子进程, pid=1630 ppid=1629 p=0
结论:调用os.fork()之后,主进程和子进程同时执行该行以下的代码,子进程中fork函数返回0,父进程中返回1630,即子进程的pid.
再看下面的代码结果:
# -*-coding:utf-8-*- import os import time print('before calling') p = os.fork() # 主进程,子进程同时向下执行 print('after calling') if p == 0: print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) time.sleep(1) print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
[root@192 ~]# python fork.py
before calling
after calling
执行主进程, pid=1648 ppid=1572 p=1649
after calling
执行子进程, pid=1649 ppid=1648 p=0
[root@192 ~]# 执行子进程, pid=1649 ppid=1 p=0
子进程先打印一段信息,再睡一秒,再打印一段信息,在子进程输出的两次信息中,ppid分别是2513和1。—— 问1:ppid怎么变了?
接下来进行探讨:
(二)fork()开启进程,主进程执行结束后,不会等待子进程:
实践:后台执行下面的代码,让主进程睡五秒,子进程睡十秒:
# -*-coding:utf-8-*- import os import time p = os.fork() if p == 0: time.sleep(10) print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: time.sleep(5) print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
[root@192 ~]# python fork.py & ### 后台执行python代码
[1] 1693
### 五秒前,查看进程信息:主进程为1693,子进程为1694
[root@192 ~]# ps aux | grep fork.py
root 1693 0.0 0.1 125432 4592 pts/0 S 21:23 0:00 python fork.py
root 1694 0.0 0.0 125432 2748 pts/0 S 21:23 0:00 python fork.py
root 1696 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
[root@192 ~]# 执行主进程, pid=1693 ppid=1572 p=1694 (此为程序打印信息,说明主进程已执行完)
[1]+ Done python fork.py
### 五秒后,主进程执行完毕,查看进程信息:只剩子进程1694
[root@192 ~]# ps aux | grep fork.py
root 1694 0.0 0.0 125432 2748 pts/0 S 21:23 0:00 python fork.py
root 1698 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
[root@192 ~]# 执行子进程, pid=1694 ppid=1 p=0 (此为程序打印信息,说明子进程已执行完。 *注意,这里ppid是1)
### 十秒后,子进程执行完毕,子进程结束
[root@192 ~]# ps aux | grep fork.py
root 1708 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
现象:五秒前,两个进程都在执行,五秒后,主进程结束,只剩一个子进程(说明父进程没有等待子进程),十秒后,子进程结束
结论:父进程执行结束后不等待子进程
问1的解释:子进程第一次打印时,刚好父进程还未结束,还可以获取到父进程ppid,因此第一次打印的ppid是父进程的pid,当睡了一秒以后,父进程早就执行完了,溜了,没有等待子进程,因此子进程交给了init进程, ppid就变成1.
(三)僵尸进程:
若子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
有什么坏处? 如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
僵尸进程的避免:
- 父进程通过os.wait()和os.waitpid等函数等待子进程结束,这会导致父进程挂起。
- 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。
- 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
- 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。
子进程变成僵尸进程,是因为父进程先执行完,没有替子进程收尸。而wait()并不是用来收尸的,只是防止父进程先于子进程退出;如果父进程先退出,会使子进程成为僵尸进程,这时候子进程的收尸就由1号init进程来回收。
主进程通过调用os.wait()等待子进程结束:
# -*-coding:utf-8-*- import os import time p = os.fork() if p == 0: time.sleep(10) print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: time.sleep(5) print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) os.wait()
[root@192 ~]# python fork.py & # 后台执行python代码
### 五秒前,查看进程信息:主进程为1751,子进程为1752
[1] 1751
[root@192 ~]# ps aux | grep fork.py
root 1751 0.5 0.1 125432 4588 pts/0 S 21:29 0:00 python fork.py
root 1752 0.0 0.0 125432 2748 pts/0 S 21:29 0:00 python fork.py
root 1754 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
[root@192 ~]# 执行主进程, pid=1751 ppid=1572 p=1752 (此为程序打印信息,说明主进程已执行到os.wait())
### 五秒后,主程序打印了信息并调用了os.wait(),查看进程信息:主进程为1751,子进程为1752,主进程没有结束
[root@192 ~]# ps aux | grep fork.py
root 1751 0.1 0.1 125436 4588 pts/0 S 21:29 0:00 python fork.py
root 1752 0.0 0.0 125432 2748 pts/0 S 21:29 0:00 python fork.py
root 1756 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
[root@192 ~]# 执行子进程, pid=1752 ppid=1751 p=0 (此为程序打印信息,说明子进程已执行完。 *注意,这里ppid不是1)
[1]+ Done python fork.py
### 十秒后,子进程执行完毕,子进程结束,父进程随着子进程的结束而结束
[root@192 ~]# ps aux | grep fork.py
root 1758 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
现象:五秒前,两个进程都在执行,五秒后,主进程执行完成并调用了os.wait(),等待子进程结束,十秒后,子进程结束,父进程也随之结束
结论:父进程可调用os.wait()等待子进程结束。 *没有子进程就调用os.wait()会抛异常: OSError: [Errno 10] No child processes
(四)进程间资源独立:
实践:在fork()函数前定义一个变量,在子进程中修改值,再从主进程中查看变量是否被修改:
# -*-coding:utf-8-*- import os import time variable = [] p = os.fork() if p == 0: variable.append(1) print('子进程 variable_id={}'.format(id(variable))) print('子进程 variable={}'.format(variable)) else: time.sleep(1) # 睡一秒,让子进程先改变变量的值 print('主进程 variable_id={}'.format(id(variable))) print('主进程 variable={}'.format(variable)) os.wait()
[root@192 ~]# python fork.py
子进程 variable_id=140426199897224
子进程 variable=[1]
主进程 variable_id=140426199897224
主进程 variable=[]
结论:子进程中改变了变量的值,但在父进程中并未改变,说明进程间全局变量不共享
问2:但为什么变量id一样?
问2的解释: 写时复制技术:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。因此不论子进程有没有修改操作,虚拟地址是一样的,两个进程查看变量的id值是相同的
参考链接:
python的os模块fork、wait、system、exec、popen、exit函数讲解
如有意见或建议,一起交流;如有侵权,请告知删除。
原文地址:https://www.cnblogs.com/Magic-Dev/p/11405448.html
- Shiro中的授权问题
- Spring Cloud中服务的发现与消费
- 使用Spring Cloud搭建高可用服务注册中心
- 从Netflix的Hystrix框架理解服务熔断和服务降级
- 使用Spring Cloud搭建服务注册中心
- 技术分享 | kafka的使用场景以及生态系统
- WebSocket刨根问底(二)
- WebSocket刨根问底(三)之群聊
- SDNLAB群分享(四):利用ODL下发流表创建VxLAN网络
- 一个简单的案例带你入门Dubbo分布式框架
- Ajax上传图片以及上传之前先预览
- Spring Cloud中Hystrix的服务降级与异常处理
- Open vSwitch源码解析之基于VxLAN实现NSH解析功能
- Spring Cloud自定义Hystrix请求命令
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- MySQL 最佳实践:程序端连接池配置
- Java获取CPU序列号
- 第018课 ADC和触摸屏硬件原理详解及裸机编程
- Mysql优化概述及其压力测试工具
- 第019课 I2C协议详解及裸机程序分析
- Redis适配采坑记
- 笔记 GWAS 操作流程6-2:手动计算GWAS分析中的GLM和Logistic模型
- 记录一次生产环境中Redis内存增长异常排查全流程!
- 移植tslib库出现selected device is not a touchscreen I understand的解决方法
- 【PHP】使用dirname(__FILE__)把目录定在需要引用的文件目录下
- php导出大量数据到excel使用轻量级的PHP_XLSXWriter比用phpexcel效率更高,2020年9月10日实测!
- 【前端JQ】使用js或jquery使button按钮变为不可用状态,并改变button上的值。
- 彻底看懂RocketMQ事务实现原理
- Tomcat NIO(10)-IO线程-关键类
- 一文揭开操作系统的神秘面纱