python fork子进程(一)

来源:互联网 发布:淘宝大全视频教程 编辑:程序博客网 时间:2024/04/29 06:52

    fork是个好动西,它通过系统调用能够创建出一个与原来进程一模一样的进程,子进程可以执行和父进程一样的代码,通过逻辑控制,也可以让父进程和子进程执行完全不同的代码块。如此好用的东西,windows是没有的,所以,赶紧转到linux吧!

一、初识fork

    创建子进程的过程非常简单

pid=os.fork()
    返回值有三种

    (1) pid < 0  表示创建失败

    (2) pid = 0   此时,处在子进程中

    (3) pid > 0  此时,处在父进程中  pid 的值就是刚刚创建出来的子进程的pid

二、 第一个简单示例

    下面看一段简单的代码:

def create_child():    pid0=os.getpid()    print '主进程',pid0    try:        pid1=os.fork()    except OSError:        print u'你的系统不支持fork'        exit()    if pid1 <0:        print u'创建子进程失败'    elif pid1==0:        print '子进程 ',pid1,os.getpid(),os.getppid()    else:        print '主进程 ',pid1,os.getpid(),os.getppid()    print u'这句话,父进程和子进程都会执行'

    如果fork函数执行成功,那么从第11行开始的代码,父进程和子进程都会执行,此时,他们已经是两个完全不相干的进程了。

三、父子内存关系

    对于fork,有一点必须搞清楚,那就是原来父进程里的那些变量和子进程里的变量是什么关系。一种普遍的存在误区的理解是子进程完全拷贝了父进程的数据段、栈和堆上的内容,但实际情况是linux引入了写时拷贝技术,子进程的页表项只想了与父进程相同的物理内存页,这样只拷贝父进程的页表项就可以了,这些页面被标记为只读,如果父子进程都不去修改内存内容,大家相安无事,一旦父子进程中的某一个尝试修改,就会引发缺页异常。此时,内核会尝试为该页面创建一个新的物理页面,并将内容真实的复制到物理页面中,这样,父子页面就各自拥有了各自的物理内存。

    看下面这段代码:

class TestFork():    def __init__(self,age):        self.age = agetf = TestFork(10)def child_work():    tf.age = os.getpid()    print 2,tf.age,tf,'\n'    while True:        print u'我是子进程',os.getpid()        time.sleep(5)def parent_work():    print 1,tf.age,tf,'\n'    while True:        print u'我是主进程',os.getpid()        time.sleep(5)def fork_many_child(count):    if count == 0:        parent_work()    pid1=os.fork()    if pid1==0:        child_work()    else:        fork_many_child(count-1)if __name__ == '__main__':    fork_many_child(3)

在父进程中,我创建了一个TestFork对象,子进程里去修改age的值,然后打印,你会发现,每个进程都有自己的tf对象,实际运行结果如下

1 10 <__main__.TestFork instance at 0x101d51950> 2 57320 <__main__.TestFork instance at 0x101d51950> 2 57321 <__main__.TestFork instance at 0x101d51950> 我是主进程 57318我是子进程 573202 57319 <__main__.TestFork instance at 0x101d51950> 

四、收尸

    如果父进程还存在,而子进程退出了,那么子进程会变成一个僵尸进程,父进程必须为他收尸。如果父进程先结束了,而子进程还没有结束,此时,子进程的父进程就变成了init进程,由它来负责为子进程退出后收尸。

    收尸有两种方法,一个是wait,一个是os.waitpid,wait是阻塞的,而os.waitpid可以设置为非阻塞的,本篇重点讲解waitpid。

    waitpid函数定义为  def waitpid(pid, options),第一个参数取值有以下几种情况:

    (1) pid > 0  等待进程ID为pid的子进程,此时是精确打击

    (2) pid = 0 等待与调用进程同一个进程组的任意子进程

    (3) pid = -1 等待任意子进程,此时和wait等价

      (4)   pid < -1 等待进程组ID与pid 绝对值相等的所有子进程

     options 是以下几个标志位的组合

     (1)    os.WNOHANG         如果子进程没有发生变化,则立刻返回,不会阻塞

  (2) os.WUNTRACED    除了关心终止进程的信息,也关心因信号而停止的子进程信息
  (3) os.WCONTINUED   除了关心终止进程的信息,也关心因受到信号而恢复执行的子进程信息
 
  函数的返回值有两个,分别为pid 和 status:
  (1) pid = 0  表示子进程没有发生变化,status不需要理会
  (2) pid = -1 表示waitpid调用失败,此时要关心status的值,status 为 ECHLD,表示没有发现有子进程需要等待
                status 为EINTR,表示函数被信号中断
  (3) pid >0   pid是发生变化的子进程的pid,具体子进程何种状态,因为什么退出,需要根据status来判断
                如果子进程是正常退出,status就是0
                如果子进程是被信号杀死的,status记录的就是终止的信号
                如果子进程被停止,或者恢复执行,status记录对应的值,这里要重点说明一下,假设你是用SIGSTOP信号停止了子进程的运行,这个status的值可不是
SIGSTOP所对应的常量值,具体是多少,取决于系统,mac下的和linux下的值是不一样的,那怎么根据status的值来判断是停止还是恢复亦或是被信号
                杀死呢,还好,系统提供了跨平台的判断方法,具体看下面的例子
  
#coding=utf-8import osimport timeimport errnodef child_work2():    # 子进程    print u'我是子进程',os.getpid()    i = 0    while True:        print u'子进程{i}'.format(i=i)        i += 1        time.sleep(3)def test_wait():    pid = os.fork()    if pid == 0:        child_work2()    else:        print u'子进程',pid        while True:            try:                time.sleep(4)                p,status = os.waitpid(pid,os.WNOHANG|os.WUNTRACED|os.WCONTINUED)            except OSError:                print u'没有子进程需要等待'                break            print p,status            if p == 0:                pass                #print u'子进程没有退出'            elif p < 0:                if status == errno.EINTR:                    print u'被信号中断'                elif status == errno.ECHILD:                    print u'该pid不可等待'            else:                if os.WIFSTOPPED(status):                    print u'子进程并没有退出,只是停止工作'                elif os.WIFCONTINUED(status):                    print u'子进程恢复了运行'                else:                    print u'子进程结束了'                    break            time.sleep(5)if __name__ == '__main__':    test_wait()


理论部分到此为止,还是来一个例子更直观
程序启动后,父进程创建一个子进程,然后开始进行waitpid操作,子进程则每隔3秒做一次输出。此时执行kill -17 + 子进程的PID,子进程会停止打印,同时
父进程会提示子进程没有退出,只是停止工作,一段时间后,再执行kill -19 +子进程PID,子进程被唤醒,继续打印,而父进程也会提示子进程恢复了运行,强调一点,
我刚才讲述的是在mac环境下,mac和linux环境下的信号所对应的常量值是不一样的,执行kill -l,可以查看信号与常量值之间的关系,如果你是linux环境下实验,
发送停止信号时应该是kill -19,发送恢复信号时,应该是kill -18。

这编辑器真难用,怎么调整都不行,难道原创的就不能有半点复制的东西么,一旦有复制就成这个鸟样。


0 0