简介                                

  程序每次执行时,操作系统都会创建一个新进程来运行程序指令。进程中可调用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核心的系统

python文档

(一)fork函数在子进程中返回0,在父进程中返回子进程的id:      

  • os.getpid()   返回进程pid

  • os.getppid()  返回父进程pid

  1. # -*-coding:utf-8-*-
  2. import os
  3. import time
  4. print('before calling')
  5.  
  6. p = os.fork() # 主进程,子进程同时向下执行
  7.  
  8. print('after calling')
  9.  
  10. if p == 0:
  11. print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  12. else:
  13. print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  1. [root@192 ~]# python fork.py
  2. before calling
  3. after calling
  4. 执行主进程, pid=1629 ppid=1572 p=1630
  5. after calling
  6. 执行子进程, pid=1630 ppid=1629 p=0

结论:调用os.fork()之后,主进程和子进程同时执行该行以下的代码,子进程中fork函数返回0,父进程中返回1630,即子进程的pid.

再看下面的代码结果:

  1. # -*-coding:utf-8-*-
  2. import os
  3. import time
  4. print('before calling')
  5.  
  6. p = os.fork() # 主进程,子进程同时向下执行
  7.  
  8. print('after calling')
  9.  
  10. if p == 0:
  11. print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  12. time.sleep(1)
  13. print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  14. else:
  15. print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  1. [root@192 ~]# python fork.py
  2. before calling
  3. after calling
  4. 执行主进程, pid=1648 ppid=1572 p=1649
  5. after calling
  6. 执行子进程, pid=1649 ppid=1648 p=0
  7. [root@192 ~]# 执行子进程, pid=1649 ppid=1 p=0

子进程先打印一段信息,再睡一秒,再打印一段信息,在子进程输出的两次信息中,ppid分别是1648和1。—— 问1:ppid怎么变了?

接下来进行探讨:

二)fork()开启进程,主进程执行结束后,不会等待子进程:        

  实践:后台执行下面的代码,让主进程睡五秒,子进程睡十秒:

  1. # -*-coding:utf-8-*-
  2.  
  3. import os
  4. import time
  5.  
  6. p = os.fork()
  7.  
  8. if p == 0:
  9. time.sleep(10)
  10. print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  11. else:
  12. time.sleep(5)
  13. print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  1. [root@192 ~]# python fork.py & ### 后台执行python代码
  2. [1] 1693
  3.  
  4. ### 五秒前,查看进程信息:主进程为1693,子进程为1694
  5.  
  6. [root@192 ~]# ps aux | grep fork.py
  7. root 1693 0.0 0.1 125432 4592 pts/0 S 21:23 0:00 python fork.py
  8. root 1694 0.0 0.0 125432 2748 pts/0 S 21:23 0:00 python fork.py
  9. root 1696 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
  10. [root@192 ~]# 执行主进程, pid=1693 ppid=1572 p=1694 (此为程序打印信息,说明主进程已执行完)
  11.  
  12. [1]+ Done python fork.py
  13.  
  14. ### 五秒后,主进程执行完毕,查看进程信息:只剩子进程1694
  15.  
  16. [root@192 ~]# ps aux | grep fork.py
  17. root 1694 0.0 0.0 125432 2748 pts/0 S 21:23 0:00 python fork.py
  18. root 1698 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
  19. [root@192 ~]# 执行子进程, pid=1694 ppid=1 p=0 (此为程序打印信息,说明子进程已执行完。 *注意,这里ppid1)
  20.  
  21. ### 十秒后,子进程执行完毕,子进程结束
  22.  
  23. [root@192 ~]# ps aux | grep fork.py
  24. 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.

(三)僵尸进程:                           

  若子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。

有什么坏处? 如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

僵尸进程的避免:

  1. 父进程通过os.wait()和os.waitpid()等函数等待子进程结束,这会导致父进程挂起。
  2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。
  3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
  4. 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。

子进程变成僵尸进程,是因为父进程先执行完,没有替子进程收尸。而wait()并不是用来收尸的,只是防止父进程先于子进程退出;如果父进程先退出,会使子进程成为僵尸进程,这时候子进程的收尸就由1号init进程来回收。

主进程通过调用os.wait()等待子进程结束:

  1. # -*-coding:utf-8-*-
  2.  
  3. import os
  4. import time
  5.  
  6. p = os.fork()
  7.  
  8. if p == 0:
  9. time.sleep(10)
  10. print('执行子进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  11. else:
  12. time.sleep(5)
  13. print('执行主进程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
  14. os.wait()
  1. [root@192 ~]# python fork.py & # 后台执行python代码
  2.  
  3. ### 五秒前,查看进程信息:主进程为1751,子进程为1752
  4.  
  5. [1] 1751
  6. [root@192 ~]# ps aux | grep fork.py
  7. root 1751 0.5 0.1 125432 4588 pts/0 S 21:29 0:00 python fork.py
  8. root 1752 0.0 0.0 125432 2748 pts/0 S 21:29 0:00 python fork.py
  9. root 1754 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
  10. [root@192 ~]# 执行主进程, pid=1751 ppid=1572 p=1752  (此为程序打印信息,说明主进程已执行到os.wait())
  11.  
  12. ### 五秒后,主程序打印了信息并调用了os.wait(),查看进程信息:主进程为1751,子进程为1752,主进程没有结束
  13.  
  14. [root@192 ~]# ps aux | grep fork.py
  15. root 1751 0.1 0.1 125436 4588 pts/0 S 21:29 0:00 python fork.py
  16. root 1752 0.0 0.0 125432 2748 pts/0 S 21:29 0:00 python fork.py
  17. root 1756 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
  18. [root@192 ~]# 执行子进程, pid=1752 ppid=1751 p=0  (此为程序打印信息,说明子进程已执行完。 *注意,这里ppid不是1)
  19.  
  20. [1]+ Done python fork.py
  21.  
  22. ### 十秒后,子进程执行完毕,子进程结束,父进程随着子进程的结束而结束
  23.  
  24. [root@192 ~]# ps aux | grep fork.py
  25. 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()函数前定义一个变量,在子进程中修改值,再从主进程中查看变量是否被修改:

  1. # -*-coding:utf-8-*-
  2.  
  3. import os
  4. import time
  5.  
  6. variable = []
  7. p = os.fork()
  8. if p == 0:
  9. variable.append(1)
  10. print('子进程 variable_id={}'.format(id(variable)))
  11. print('子进程 variable={}'.format(variable))
  12. else:
  13. time.sleep(1) # 睡一秒,让子进程先改变变量的值
  14. print('主进程 variable_id={}'.format(id(variable)))
  15. print('主进程 variable={}'.format(variable))
  16. os.wait()
  1. [root@192 ~]# python fork.py
  2. 子进程 variable_id=140426199897224
  3. 子进程 variable=[1]
  4. 主进程 variable_id=140426199897224
  5. 主进程 variable=[]

结论:子进程中改变了变量的值,但在父进程中并未改变,说明进程间全局变量不共享

问2:但为什么变量id一样?

问2的解释:  写时复制技术:内核只为新生成的子进程创建虚拟空间结构,它们复制于父进程的虚拟空间结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。因此不论子进程有没有修改操作,虚拟地址和父进程是一样的,两个进程查看变量的id值是相同的。

另外,即使是两个互不相干的进程,若它们的逻辑地址相同,实际地址也是不同的,并不会产生冲突。

参考链接:

百度百科僵尸进程

python的os模块fork、wait、system、exec、popen、exit函数讲解

父进程调用wait函数究竟对子进程做了什么?

Linux进程管理——fork()和写时复制

如有意见或建议,一起交流;如有侵权,请告知删除。

python多进程——fork()的更多相关文章

  1. python 多进程开发与多线程开发

    转自: http://tchuairen.blog.51cto.com/3848118/1720965 博文作者参考的博文:  博文1  博文2 我们先来了解什么是进程? 程序并不能单独运行,只有将程 ...

  2. Python 多进程 多线程 协程 I/O多路复用

    引言 在学习Python多进程.多线程之前,先脑补一下如下场景: 说有这么一道题:小红烧水需要10分钟,拖地需要5分钟,洗菜需要5分钟,如果一样一样去干,就是简单的加法,全部做完,需要20分钟:但是, ...

  3. 深入理解python多进程编程

    1.python多进程编程背景 python中的多进程最大的好处就是充分利用多核cpu的资源,不像python中的多线程,受制于GIL的限制,从而只能进行cpu分配,在python的多进程中,适合于所 ...

  4. python多进程-----multiprocessing包

    multiprocessing并非是python的一个模块,而是python中多进程管理的一个包,在学习的时候可以与threading这个模块作类比,正如我们在上一篇转载的文章中所提,python的多 ...

  5. python多进程multiprocessing Pool相关问题

    python多进程想必大部分人都用到过,可以充分利用多核CPU让代码效率更高效. 我们看看multiprocessing.pool.Pool.map的官方用法 map(func, iterable[, ...

  6. Python多进程编程

    转自:Python多进程编程 阅读目录 1. Process 2. Lock 3. Semaphore 4. Event 5. Queue 6. Pipe 7. Pool 序. multiproces ...

  7. Python多进程(1)——subprocess与Popen()

    Python多进程方面涉及的模块主要包括: subprocess:可以在当前程序中执行其他程序或命令: mmap:提供一种基于内存的进程间通信机制: multiprocessing:提供支持多处理器技 ...

  8. Python多进程使用

    [Python之旅]第六篇(六):Python多进程使用   香飘叶子 2016-05-10 10:57:50 浏览190 评论0 python 多进程 多进程通信 摘要:   关于进程与线程的对比, ...

  9. python多进程断点续传分片下载器

    python多进程断点续传分片下载器 标签:python 下载器 多进程 因为爬虫要用到下载器,但是直接用urllib下载很慢,所以找了很久终于找到一个让我欣喜的下载器.他能够断点续传分片下载,极大提 ...

随机推荐

  1. pycharm鼠标更改字体大小

    Pycharm快捷键设置(鼠标滚动控制字体大小) 一.pycharm字体放大的设置 File -> setting -> Keymap ->在搜寻框中输入:increase -> ...

  2. 2019牛客多校A All-one Matrices——单调栈

    题目 求非嵌套子矩阵的个数. 分析 单调栈的套路题(类似的有求最大子矩阵). 首先,按列预处理,每个位置化成连续1的个数. 例如,左边的图转成右边.                      然后枚举 ...

  3. python - django (auth 的使用)

    # """ 1. 创建用户: python manage.py createsuperuser 2. from django.contrib import auth au ...

  4. AirTest与模拟器连接(二)

    如果我们手边没有可用的Android真机,又想进行Android应用自动化测试,这时候就要使用AirtestIDE的Android模拟器自动化测试功能了. AirtestIDE所支持的模拟器包括 An ...

  5. 【解题报告】CSP2019-S D1T1 格雷码

    题目链接:https://www.luogu.org/problem/P5657 话说这道题怎么是道橙题啊. 基本思路 因为n位格雷码的前2n-1位就是n-1位格雷码前面加了一位‘0’,所以可以把它们 ...

  6. Oracle 与 postgreSQL 事务处理区别(多版本与undo区别)

    2015年左右,因为工作需要用MongoDB.CouchBase这两种文档型数据库,时不时到这两个数据库官网上查资料.报BUG.时常可以在MongoDB官网上看到这样一些新闻,“某某企业成功将MySQ ...

  7. 四十七.iptables防火墙 filter表控制 扩展匹配 nat表典型应用

    1.iptables基本管理 关闭firewalld,开启iptables服务 查看防火墙规则 追加.插入防火墙规则 删除.清空防火墙规则   1.1 关闭firewalld,启动iptables服务 ...

  8. pyexcel_xlsx

    from pyexcel_xlsx import get_data,save_data excel_data = get_data('xxxx.xlsx文件存储位置') #得到的excel_data是 ...

  9. start django project

    1.django-admin startproject helloword 创建项目helloword 2.开始一个app,写一个hello worldpython manage.py startap ...

  10. Hadoop zookeeper hbase spark phoenix (HA)搭建过程

    环境介绍: 系统:centos7 软件包: apache-phoenix-4.14.0-HBase-1.4-bin.tar.gz  下载链接:http://mirror.bit.edu.cn/apac ...