Python:os.fork()如何工作?

时间:2015-11-06 06:32:25

标签: python fork

我在python中学习多处理。我尝试了多处理,在我阅读了多处理模块的源代码之后,我发现它使用os.fork(),所以我写了一些代码来测试os.fork(),但是我被卡住了。我的代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import time

for i in range(2):
    print '**********%d***********' % i
    pid = os.fork()
    print "Pid %d" % pid

我认为每次打印都会执行两次,但执行三次。我无法理解这项工作如何? 我读了这个Need to know how fork works?
从这篇文章的内容来看,它也会被执行两次,所以我很困惑......

3 个答案:

答案 0 :(得分:37)

首先,删除print '******...'行。它只会让每个人感到困惑。相反,让我们试试这段代码......

import os
import time

for i in range(2):
    print "I'm about to be a dad!"
    time.sleep(5)
    pid = os.fork()
    if pid == 0:
        print "I'm {}, a newborn that knows to write to the terminal!".format(os.getpid())
    else:
        print "I'm the dad of {}, and he knows to use the terminal!".format(pid)
        os.waitpid(pid)

好的,首先,什么是“分叉”? Fork 是现代和标准兼容操作系统的一个功能(除了M $ Windows:操作系统的笑话完全是现代的和符合标准的)允许进程(又名:“程序”) ,这包括Python解释器!)从字面上完全复制自己,有效地创建一个新的进程(“程序”的另一个实例)。一旦魔术完成,两个过程都是独立的。改变其中一个中的任何内容都不会影响另一个。

负责拼写这个黑暗和古老咒语的过程被称为父进程。这种对生命本身的憎恶的无灵魂结果被称为儿童过程。

对于所有人来说都是显而易见的,包括那些不是的人,你可以成为那些通过os.fork()卖出灵魂的精选程序员的一员。此函数执行fork操作,从而导致第二个进程凭空创建。

现在,这个函数返回了什么,或者更重要的是, 甚至返回了什么?如果你不想变得疯狂,不要去阅读Linux内核的/kernel/fork.c文件!一旦内核完成我们知道它必须做的事情,但我们不想接受它,os.fork()返回两个进程!是的,甚至复制了调用堆栈!

所以,如果他们是完全相同的副本,父母和孩子之间的差异如何?简单。如果os.fork()的结果为零,那么您正在为孩子工作。否则,您正在父项中工作,返回值是子项的PID(进程标识符)。无论如何,孩子可以从os.getpid()获得自己的PID,不是吗?

现在,考虑到这一点,以及在循环中执行fork()的事实是混乱的秘诀,这就是发生的事情。让我们将原始流程称为“主”流程......

  • 主人:i = 0,分叉给孩子 - #1-of-master
    • 孩子 - #1-of-master:i = 1分叉给孩子 - #1-of-child-#1-of-master
    • 孩子 - #1-of-child-#1-of-master:for循环,退出
    • 孩子 - #1-of-master:for循环,退出
  • 主人:i = 1,分叉给孩子 - #2-of-master
    • 孩子 - #2-of-master:i = 1分叉给孩子 - #1-of-child-#2-of-master
    • 孩子 - #1-of-child-#2-of-master:for循环,退出
    • 孩子 - #2-of-master:for循环,退出
  • 主人:for循环,退出

正如您所看到的,共有6个父/子打印来自4个独特的过程,产生6行输出,类似......

  

我是12120的父亲,他知道要使用终端!

     

我是12120,一个知道写信给终端的新生儿!

     

我是12121的父亲,他知道要使用终端!

     

我是12121,一个知道要写到终端的新生儿!

     

我是12122的父亲,他知道要使用终端!

     

我是12122,一个知道要写到终端的新生儿!

但这只是武断的,它可能会输出这个......

  

我是12120,一个知道写信给终端的新生儿!

     

我是12120的父亲,他知道要使用终端!

     

我是12121,一个知道要写到终端的新生儿!

     

我是12121的父亲,他知道要使用终端!

     

我是12122,一个知道要写到终端的新生儿!

     

我是12122的父亲,他知道要使用终端!

或者其他任何东西。操作系统(以及您主板的时髦时钟)是唯一负责进程获取时间片的顺序的,如果您不喜欢内核如何组织您的进程,请转到blame on Torvalds (and expect no self-steem when back)。)。

我希望这能为你带来一些启示!

答案 1 :(得分:20)

要直接回答问题,{​​{1}}通过调用底层操作系统函数os.fork()来工作。

但你肯定对这件事感兴趣。那么这会创建另一个过程,它将在与此完全相同的位置恢复。所以在第一个循环运行中,你得到一个fork,之后你有两个进程,“原始的”(得到子进程的PID的fork()值)和分叉的进程(得到一个{ {1}} pid}的值。

他们都打印他们的pid值并继续进行第二次循环运行,他们都打印出来。然后他们都会分叉,给你留下4个进程,这些进程都会打印各自的0值。其中两个应该是pid,另外两个应该是他们刚创建的孩子的PID。

将代码更改为

pid

你会看到更好的结果:每个进程都会告诉你它自己的PID和fork上发生了什么。

答案 2 :(得分:1)

这两个答案几乎涵盖了有关os.fork()工作原理的所有内容。我想添加一些有关此案的更具体的信息。

Fork系统调用用于创建一个称为子进程的新进程,该子进程与进行fork()调用的进程(父进程)同时运行。在创建新的子进程之后,两个进程将执行(执行状态;堆,堆栈和处理器寄存器)在fork()系统调用(创建与 调用方将复制文本,数据,堆栈和堆)。 fork()不接受参数,并返回一个整数值。成功调用fork()之后,子进程基本上与父进程完全相同。 fork是Unix系统调用,用于创建新进程。从内核请求服务的系统调用方式。 fork返回一个值,如果该值大于0,则表示成功进行fork,但是您也可能会小于0,这可能是因为进程内存不足或其他错误,无法创建该进程,并且会引发错误。如果等于零,则它是大于0的子级,这意味着父进程。因此该过程同时运行,我们将并行化存档在这里,但是fork调用之后的所有操作都将执行。

呼叫fork重复了被称为父进程的过程。 fork表示两个不同的地址基数相同副本,一个用于子进程,一个用于父进程。除PID和其他几个散文外,这两个散文都是彼此的精确复制。由于两个进程具有相同但独立的地址空间,因此在fork()调用之前初始化的那些变量在两个地址空间中都具有相同的值。由于每个进程都有自己的地址空间,因此任何修改都将独立于其他进程。换句话说,如果父级更改其变量的值,则修改将仅影响父级进程地址空间中的变量。即使fork()调用创建的其他地址空间具有相同的变量名,也不会受到影响。

在fork()上,父级拥有的所有资源均被复制,并将副本提供给子级。在Linux中,fork()通过使用写时复制页面实现。写时复制或简单地CoW是一种资源管理技术。有关CoW

的更多信息

如果要创建具有多个进程的并行程序,则可以使用fork。

相关问题