python多进程——fork()

系统 420 0

  简介                                

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

            
              #
            
            
               -*-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分别是1648和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.

(三)僵尸进程:                            

 

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

 

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

 僵尸进程的避免:

  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()等待子进程结 束:

            
              #
            
            
               -*-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函数讲解

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

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

 

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

 


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请请扫描上面二维码支持博主1元、2元、5元等您想捐的金额吧,狠狠点击下面给点支持吧

发表我的评论
最新评论 总共0条评论