如何创建进程及进程的特性

Posted dominic-ji

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何创建进程及进程的特性相关的知识,希望对你有一定的参考价值。

1.必备知识点,

  什么是进程:程序被执行的过程,就叫进程。计算机内部原理,由操作系统控制硬盘操作将程序读入内存,调用cpu来执行程序。内存中进程与进程之间的内存空间是绝对物理意义上的隔离的。启动qq,微信,word三个程序,就会在内存中开辟三块独立的内存空间来存放它们,而如果一个程序被你启动了好几次(鼠标点了好几下)那么也是不同的进程,在内存中也是对应有互相隔离的内存空间——即进程与进程之间内存空间也是彼此隔离的(无论是不同的程序之间还是相同的程序亦或是下面介绍的父程序申请创建主程序方式~)

2.创建子进程的两种方式

PS:在程序中再创建子进程的意义在于,想让程序中的任务能够并发去执行!

第一种:

from multiprocessing import Process
import time

def task():
    print(子程序开始运行》》》)
    time.sleep(3)
    print(主程序运行完毕》》》)

if __name__ == __main__:
    p=Process(target=task)
    p.start()
    print(主程序代码读取完毕!)

#运行结果
# 主程序代码读取完毕!
# 子程序开始运行》》》
    #间隔三秒后打印下面的结果
# 主程序运行完毕》》》
技术分享图片
首先导入的是专门用来产生子进程的模块Process是一个类
导入的time模块是为了模拟子进程执行了一段时间
if __name__==__main__:
    先调用Process类传入参数产生子进程对象 
    在windows环境下创建子进程的代码必须写在上述条件下面,
因为程序值执行到p.start()时,其实就是向操作系统发出申请说我需要开一个子进程
这个操作系统会开辟单独的内存空间,之后重新导入当前文件并运行,将产生的名称空间作为子进程的初始数据库放入新开辟的内存空间中(无论是windows系统还是linux系统,子进程的产生都需要拷贝父进程的数据作为起始运行状态),
当主程序发出申请代码块后,就不会再去管子进程什么时候创建运行(程序也决定不了这个事,而是由操作系统说了算),而继续去运行自身接下来的代码,
上述代码解析

第二种:

from multiprocessing import Process
import time
class Myprocess(Process):
    def __init__(self,name):
        super(Myprocess,self).__init__()
        self.name=name
    def run(self):
        print(%s is running %self.name)
        time.sleep(3)
        print(%s is running over%self.name)
if __name__ == __main__:
    p=Myprocess(alex)
    p.start()
    print(主程序代码运行完毕)
技术分享图片
既然用来产生子进程的模块是一个类,那么我们学过面向对象及元类,对类的使用应该也是有一定水平的,我们就可以自己为子进程定制一些专属的特性(后面会写一个为子进程定制一个同时获取子进程和父进程pid的小方法)
首先定义我们自己的类Myprocess继承Process,在调用super()先继承父类的初始化方法,在给子进程添加一个名字属性
而下面的run()方法是必须要加的,也就是说每一个用来产生子进程的类中肯定对应有一个run()方法,这个run()对应的就是对象调用start()时触发,run()方法里写的就是你想让子进程想干的事情,就是第一种创建子进程的task函数啦~
上述代码解读

3.验证父进程与子进程之间空间是绝对物理意义上的互相隔离

from multiprocessing import Process
x=1000
def task():
    global x
    x=1
    print(子进程运行完毕)

if __name__ == __main__:
    p=Process(target=task)
    p.start()
    p.join()
    print(主进程的变量X:%s%x)
    #result:
            子进程运行完毕
            主进程的变量X:1000  
#这里先在父程序名称空间中定义一个全局变量x=1000,如何产生一个子进程,子进程会拷贝父类代码名称空间,
#这个时候在子进程函数方法中通过global方法将x的修改变为全局有效,这个时候运行完结果是父程序中变量的值并没有改变,说明子进程并不能影响到父进程的名称空间,
#这里子进程中的修改操作确实执行了,只不过子进程修改的是子进程自己的名称空间中的x的值

4.父进程在执行带申请创建子进程后原地等待子进程运行结束再去运行自身下面的代码~(join()方法)‘

from multiprocessing import Process
import time
def task():
    print(子程序开始运行)
    time.sleep(3)
    print(子程序运行完毕)
if __name__ == __main__:
    p=Process(target=task)
    p.start()
    p.join()
    print(主程序代码读取完毕!)
#结果:
#子程序开始运行
# 子程序运行完毕
# 主程序代码读取完毕!
#这里的join()就是让主程序等着子进程运行结束才能继续往下走,join()在这里并不仅仅是这个效果,起始涉及到了后面要说的僵尸进程和孤儿进程(针对uniux系统),
join()是为了将子进程运行完毕后产生的数据全部回收清空(了解即可)

这里需要明确的是join()是让谁等?是主进程等,卡住的是主进程而绝非其他的进程~~~

5.获取进程的PID(getpid(),getppid())

  在计算机每个进程的运行计算机都会随机分配给进程一个PID号,相当于我们的身份证一样,每台计算机中pid号的数量是有限的,那如何获取子进程的pid号呢?

from multiprocessing import Process
import time,os
x=1000
def task():
    print(self:%s,parent:%s%(os.getpid(),os.getppid()))
    time.sleep(3)
if __name__ == __main__:
 p1=Process(target=task)
 p1.start()
 print(p1.pid)#在外部获取子进程的pid,直接调子进程自带的属性,这里可以自己为其定制,通过第二种创建子进程的方式自定义类!
 print(主程序的:%s%os.getpid())
#结果:
10144
主程序的:10724
self:10144,parent:10724

6.僵尸进程与孤儿进程(针对uniux,linux系统)

技术分享图片
参考博客:http://www.cnblogs.com/Anker/p/3271773.html

一:僵尸进程(有害)
  僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下

我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。

因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

  任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。  如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

二:孤儿进程(无害)

  孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

  孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容

import os
import sys
import time

pid = os.getpid()
ppid = os.getppid()
print im father, pid, pid, ppid, ppid
pid = os.fork()
#执行pid=os.fork()则会生成一个子进程
#返回值pid有两种值:
#    如果返回的pid值为0,表示在子进程当中
#    如果返回的pid值>0,表示在父进程当中
if pid > 0:
    print father died..
    sys.exit(0)

# 保证主线程退出完毕
time.sleep(1)
print im child, os.getpid(), os.getppid()

执行文件,输出结果:
im father pid 32515 ppid 32015
father died..
im child 32516 1

看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。


三:僵尸进程危害场景:

  例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。

四:测试
#1、产生僵尸进程的程序test.py内容如下

#coding:utf-8
from multiprocessing import Process
import time,os

def run():
    print(,os.getpid())

if __name__ == __main__:
    p=Process(target=run)
    p.start()
    
    print(,os.getpid())
    time.sleep(1000)


#2、在unix或linux系统上执行
[[email protected] ~]# python3  test.py &
[1] 18652
[[email protected]-31-0-19 ~]# 主 18652
子 18653

[[email protected]-31-0-19 ~]# ps aux |grep Z
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     18653  0.0  0.0      0     0 pts/0    Z    20:02   0:00 [python3] <defunct> #出现僵尸进程
root     18656  0.0  0.0 112648   952 pts/0    S+   20:02   0:00 grep --color=auto Z

[[email protected]-31-0-19 ~]# top #执行top命令发现1zombie
top - 20:03:42 up 31 min,  3 users,  load average: 0.01, 0.06, 0.12
Tasks:  93 total,   2 running,  90 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1016884 total,    97184 free,    70848 used,   848852 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   782540 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
root      20   0   29788   1256    988 S  0.3  0.1   0:01.50 elfin                                                                                                                      


#3、
等待父进程正常结束后会调用wait/waitpid去回收僵尸进程
但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的
解决方法一:杀死父进程
解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
参考python2源码注释
class Process(object):
    def join(self, timeout=None):
        ‘‘‘
        Wait until child process terminates
        ‘‘‘
        assert self._parent_pid == os.getpid(), can only join a child process
        assert self._popen is not None, can only join a started process
        res = self._popen.wait(timeout)
        if res is not None:
            _current_process._children.discard(self)

join方法中调用了wait,告诉系统释放僵尸进程。discard为从自己的children中剔除

解决方法三:http://blog.csdn.net/u010571844/article/details/50419798
僵尸进程与孤儿进程

  其实不管实在哪个操作平台上,子进程都会经历僵尸进程这一阶段,而主进程在没有可以干扰他的代码读取情况下,大家会发现,主进程虽然代码读取完毕了,但是它还是不会结束,而是要等着所有的子进程全部结束才会结束,这就是为了回收子进程的尸体(数据)。详细的请看上述缩进内容

 



以上是关于如何创建进程及进程的特性的主要内容,如果未能解决你的问题,请参考以下文章

java 简单的代码片段,展示如何将javaagent附加到运行JVM进程

如何在Go代码超时时杀死进程及其子进程?

守护进程模型创建思路及详细实现代码

linux--bash基础及特性

在 Python 多处理进程中运行较慢的 OpenCV 代码片段

详解进程 及 探查进程