zoukankan      html  css  js  c++  java
  • fork()和僵尸进程

    2018-01-03@望京

    关于fork()函数,Unix/Linux提供的fork()系统调用,fork()一次返回两次,

    操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回;

    子进程永远返回 0,而父进程返回子进程的ID。

    父进程结束时,子进程并不会随父进程立刻结束;同样,父进程不会等待子进程执行完。

      - Python里fork()的使用

    示例一

    #!/usr/bin/python3
    
    import os
    
    print('Process (%s) start...' % os.getpid())
    # Only works on Unix/Linux/Mac
    pid = os.fork()
    if pid == 0:
        print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
    else:
        print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
    
    
    ####### 结果 #########
    Process (25912) start...
    I (25912) just created a child process (25913).
    I am child process (25913) and my parent is 25912.

    示例二

    #!/usr/bin/python3
    
    import os
    import time
     
    #创建子进程之前声明的变量
    source = 10
    try:
        pid = os.fork()
        if pid == 0: #子进程
            print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
            #在子进程中source自减1
            source -= 1
            time.sleep(1)
        else: #父进程
            print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
        print(source)
        time.sleep(2)
    except OSError as e:
        print("exception ",e)
    
    ####### 结果 ########
    I (12807) just created a child process (12808).
    10
    I am child process (12808) and my parent is 12807.
    9
    

      

      - 服务器里的僵尸进程是怎么产生的?

    参考:https://www.cnblogs.com/yuxingfirst/p/3165407.html

    参考:http://blog.csdn.net/qq_20218109/article/details/52078076

    参考:http://blog.51cto.com/forlinux/1422438

    子进程结束后,但是父进程还没有结束的时候,子进程是出于Zombie状态的,这个需要父进程去收集子进程的信息释放子进程

    如果父进程结束了子进程没有结束,那么子进程就会寄托给pid为1的进程来管理。

    给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取,

    这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等);

    僵尸进程的产生示例

    子进程先于父进程退出,同时父进程又没有调用wait/waitpid,则该子进程将成为僵尸进程。

    #!/usr/bin/python3
    
    import os
    import time
     
    try:
        pid = os.fork()
        if pid == 0: #子进程
            print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
        else: #父进程
            print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
            time.sleep(50)
        print('%s end' % os.getpid())
    except OSError as e:
        print("exception ",e)
    
    
    ###### 结果 ######
    I (19668) just created a child process (19669).
    I am child process (19669) and my parent is 19668.
    19669 end
    19668 end   # 这里sleep了50秒才打印

    程序运行时,结合ps命令查看僵尸进程情况:

    root@10.13.17.16[13:30:40]$ ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'
    Z+   19668 19669 [forkkk.py] <defunct>
    root@10.13.17.16[13:31:28]$ 
    root@10.13.17.16[13:31:28]$ ps -A -ostat,ppid,pid,cmd | grep forkkk
    S+   10582 19668 /usr/bin/python3 ./forkkk.py
    Z+   19668 19669 [forkkk.py] <defunct>    # 显然pid是19669,即子进程的id,子进程已经执行结束了,但是此时父进程19668还没有执行完。
    S+   17673 19678 grep forkkk
    root@10.13.17.16[13:31:30]$ 
    

      

      - 如何避免出现僵尸进程呢/如何回收僵尸进程?

    1. 信号方式

    当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,

    所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的。

    #!/usr/bin/python3
    
    import os
    import time
    import signal
    
    def childhandler(*args, **kwargs):
        while True:
            try:
                result = os.waitpid(-1, os.WNOHANG)
            except:
                break
            else:
                print("Reaped child process %(pid)d, status is %(status)s" % 
                    {'pid':result[0], 'status':result[1]})
    
    if __name__ == '__main__':
        signal.signal(signal.SIGCHLD, childhandler)
        print("Before the fork, parent PID is %s" % os.getpid())
        pid = os.fork()
        if pid:
            print("Hello from the parent, the child PID is %s" % pid)
            print("Parent sleeping 10 seconds ...")
            time.sleep(10)
            print("Parent sleep done.")
        else:
            print("Child sleeping 3 seconds ...")
            time.sleep(3)
            print("Child sleep done.")
    
    ###### 结果 ######
    Before the fork, parent PID is 18998
    Hello from the parent, the child PID is 18999
    Parent sleeping 10 seconds ...
    Child sleeping 3 seconds ...
    Child sleep done.
    Reaped child process 18999, status is 0
    Parent sleep done.
    
    os.waitpid()
    
    -1            表示等待主进程的所有子进程终止
    os.WNOHANG    表示如果没有已经终止的子进程就立即返回

    2. 父进程轮询检查子进程并回收

    #!/usr/bin/python3
    
    import os
    import time
    
    def recovery():
        while True:
            try:
                result = os.waitpid(-1, os.WNOHANG)
                #result = os.wait()
            except:
                break
            else:
                return result
    
    if __name__ == '__main__':
        print("Before the fork, parent PID is %s" % os.getpid())
        pid = os.fork()
        if pid:
            print("Hello from the parent, the child PID is %s" % pid)
            while True:
                res = recovery()
                time.sleep(0.5)
                print('one loop...')
                if res[0]:
                    print("Reaped child process %(pid)d, status is %(status)s" % 
                        {'pid':res[0], 'status':res[1]})
                    break
            print("Parent run end...")
        else:
            print("Child sleeping 3 seconds ...")
            time.sleep(3)
            print("Child sleep done.")
    
    ###### 结果 ######
    Before the fork, parent PID is 3391
    Hello from the parent, the child PID is 3392
    Child sleeping 3 seconds ...
    one loop...
    one loop...
    one loop...
    one loop...
    one loop...
    one loop...
    Child sleep done.
    one loop...
    one loop...
    Reaped child process 3392, status is 0
    Parent run end...

    轮询的方式没有办法很及时的处理僵尸进程(取决于轮询的时间间隔)。

    注意:

        - result = os.waitpid(-1, os.WNOHANG)  这种方式不会阻塞调用者

        - result = os.wait()  这种会阻塞调用者,导致不会进行轮询。

    3. fork()两次/Python实现守护进程

    父进程一次fork()后产生一个子进程,随后父进程立即执行waitpid(子进程pid, NULL, 0)来等待子进程结束,然后子进程fork()后产生孙子进程随后立即exit(0)

    这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行

    这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管

    于是父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其孙子进程结束时会自动收尸,这样也就不会产生僵尸进程了。

    详情见:http://blog.tangyingkang.com/post/2016/10/20/python-daemon/ *****

    fork()两次不是必须的:https://segmentfault.com/a/1190000008556669 *****

    import os
    import sys
    import atexit
    
    def daemonize(pid_file=None):
        """
        创建守护进程
        :param pid_file: 保存进程id的文件
        :return:
        """
        # 从父进程fork一个子进程出来
        pid = os.fork()
        # 子进程的pid一定为0,父进程大于0
        if pid:
            # 退出父进程,sys.exit()方法比os._exit()方法会多执行一些刷新缓冲工作
            sys.exit(0)
    
        # 子进程默认继承父进程的工作目录,最好是变更到根目录,否则回影响文件系统的卸载
        os.chdir('/')
        # 子进程默认继承父进程的umask(文件权限掩码),重设为0(完全控制),以免影响程序读写文件
        os.umask(0)
        # 让子进程成为新的会话组长和进程组长
        os.setsid()
    
        # 注意了,这里是第2次fork,也就是子进程的子进程,我们把它叫为孙子进程
        _pid = os.fork()
        if _pid:
            # 退出子进程
            sys.exit(0)
    
        # 此时,孙子进程已经是守护进程了,接下来重定向标准输入、输出、错误的描述符(是重定向而不是关闭, 这样可以避免程序在 print 的时候出错)
    
        # 刷新缓冲区先,小心使得万年船
        sys.stdout.flush()
        sys.stderr.flush()
    
        # dup2函数原子化地关闭和复制文件描述符,重定向到/dev/nul,即丢弃所有输入输出
        with open('/dev/null') as read_null, open('/dev/null', 'w') as write_null:
            os.dup2(read_null.fileno(), sys.stdin.fileno())
            os.dup2(write_null.fileno(), sys.stdout.fileno())
            os.dup2(write_null.fileno(), sys.stderr.fileno())
    
        # 写入pid文件
        if pid_file:
            with open(pid_file, 'w+') as f:
                f.write(str(os.getpid()))
            # 注册退出函数,进程异常退出时移除pid文件
            atexit.register(os.remove, pid_file)
    

      - 扩展:Python中fork的代价

    在Unix系统上面启动守护进程

    作者:Standby一生热爱名山大川、草原沙漠,还有妹子
    出处:http://www.cnblogs.com/standby/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    HTML和CSS
    springcloud中配置多数据源
    洛谷 P3800 Power收集(单调队列优化dp)
    洛谷 P2569 [SCOI2010]股票交易(单调队列优化dp)
    洛谷 P3957 [NOIP2017 普及组] 跳房子(二分,单调队列优化dp)
    洛谷 P1419 寻找段落(01分数规划,实数二分,单调队列)
    Prometheus(普罗米修斯)和grafana监控的安装和使用
    AMC如何管理NPL
    linux 正则表达式与字符处理
    react-router-dom switch & match
  • 原文地址:https://www.cnblogs.com/standby/p/8280606.html
Copyright © 2011-2022 走看看