python fork()多进程

来源:互联网 发布:淘宝网中老年女衬衫 编辑:程序博客网 时间:2024/05/18 09:12

一、理解fork()

fork()是一个绝对唯一的调用。Python中的大多数函数会之返回一次,因为sys.exit()会终止程序,所以它就不会返回。相比之下,Python的os.fork()是唯一返回两次的函数,任何返回两次的函数,在某种意义上,都可以调用os.fork()来实现。在调用fork()之后,就同时存在两个正在运行程序的拷贝。但是第二个拷贝并不是从开始就重新开始的。两个拷贝在对fork()调用后会继续——进程的整个地址空间被拷贝。这时可能会出现错误,而os.fork()可以产生异常。

对fork的调用,返回针对父进程而产生新进程的PID。对于子进程,它返回PID 0.因此,它的逻辑如下:

def handle():    pid = os.fork()    if pid:        #parent        close_child_connections()        handle_more_connections()    else:        #child        close_parent_connections()        process_this_connections()
二、zombie进程

fork()的语义是建立在父进程对找出子进程什么时候,以及如何终止感兴趣的假定上的。例如,一个shell脚本会对找出正在运行的程序中的退出代码感兴趣。父进程不仅可以找出退出代码,还可以找出根据信号,进程是坏掉还是终止。父进程是通过os.wait()或一个类似的调用来得到这些信息的。

在子进程终止和父进程调用wait()之间的这段时间,子进程被成为zombie进程。它停止了运行,但是内存结构还为允许父进程执行wait()保持着。在子进程终止后,必须调用wait()函数,否则系统系统资源会被大量的zombie进程消耗掉,最终会使服务器不可用。

操作系统可以非常容易地完成这个工作。每当子进程终止的时候,它会向父进程发送SIGCHLD信号(信号是一个通知进程某些事件的基本方法)。父进程可以设置一个信号处理程序来接受SIGCHLD和整理已经终止的子进程。

如果父亲进程在子进程之前终止,子进程会一直执行。系统会通过把它们的父进程设置为init(PID 1)来重新制定父进程。init进程就会负责清楚zombie进程。

三、fork()性能

由于fork()函数每次在客户端连接的时候必须在整个服务器中拷贝,所以或许有人会认为它是一个很慢的方法。事实上,fork()的性能对于几乎所有具有高负载的系统来说是可忽略的。

大多数的操作系统,例如linux,是通过copy-on-write内存来实现fork()的。这就意味着,只有内存需要被拷贝(当有进程要修改它)的时候,它才会真正被拷贝。实际上,对fork()的调用通常是瞬间的。

对fork()的调用是应用在整个系统中的。例如,当使用Shell,输入ls,Shell就会调用fork()来产生一个fork的拷贝,新的进程将调用ls。

四、fork()示例

#!/usr/bin/env python#coding:utf-8import os,timeprint 'before the fork,my PID is',os.getpid()if os.fork():print 'Hello from the parent. My PID is',os.getpid()else:print 'Hello from the child. My PID is',os.getpid()time.sleep(1)print 'Hello from both of us.'

两个进程应该同时执行,当程序执行到该点的时候,实际上存在着两个程序的拷贝在执行。所以问候语在代码中只出现一次,而结果中却显示两次。

五、zombie示例

#!/usr/bin/env pythonimport os,timeprint 'Before the fork,my PID is',os.getpid()pid = os.fork()if pid:print 'Hello from the parent.The child will be PID %d' % pidprint 'Sleeping 120 seconds...'time.sleep(120)

子进程会在fork()之后立刻终止,父进程在sleep,能看出子进程出现了zombie,可以从第三列中的Z和输出最后的<defunct>看出来。一旦父进程终止了,将可以确定两个进程都不存在了。

六、使用信号解决zombie问题

#!/usr/bin/env pythonimport os,time,signaldef chldHandler(signum,stackframe):while 1:try:result = os.waitpid(-1,os.WNOHANG)except:breakprint 'Reaped child process %d' % result[0]signal.signal(signal.SIGCHLD,chldHandler)print 'before the fork,my PID is:',os.getpid()pid = os.fork()if pid:print 'Hello from the parent.The child will be PID %d' %pidprint 'Sleeping 10 seconds...'time.sleep(10)print 'Sleep done.'else:print 'Child sleeping 5 seconds...'time.sleep(5)

首先,这个程序定义了信号处理程序chldhandler()。每次收到SIGCHLD的时候,就会调用这个函数。它有一个简单的循环调用os.waitpid(),它的第一个参数-1,意思是等待所有的已经终止的子程序,而第二个参数是说如果没有已经终止的进程存在,就立刻返回。如果有子进程在等待,waitpid()返回一个进程的PID的tuple和退出信息。否则,它产生一个异常。使用wait()或waitpid()来搜集终止进程的信息被称为收割(reaping).

示例中子进程睡眠5秒钟后,父进程就开始收割。time.sleep()有一种特殊情况,如果任意一个信号处理程序被调用,睡眠会被立刻终止,而不是继续等待剩余的时间。

七、总结

大多数服务器都需要同时处理多个客户端。对于服务器的设计者来说,有几种方法可以实现它,其中最简单的就是forking,它主要适用于Linux和UNIX平台。

为了使用fork,需要调用os.fork(),它会返回两次。这个函数把子进程的进程ID返回给父进程,还会把零值返回给子进程。

当某个进程终止的时候,除非该进程的父进程调用了wait()或waitpid(),否则终止信息会一直保持在系统上。因此使用foring的程序必须确保在子进程终止时要调用wait()或waitpid(),方法之一是信号处理程序,还可以使用轮询(polling),定期检查终止的子程序。

使用forking的服务器通常会调用fork()来为每一个到来的连接建立一个新进程。对于进程中不使用的文件描述符,重要的一点是父进程和子进程都应该关闭。

如果文件被修改,锁定是非常重要的。锁定可以避免数据损坏。如果多个进程同时修改一个文件,或者一个进程读取文件的时候,另一个进程正在写文件,都会损坏文件。

如果系统不能执行fork,os.fork()函数可以产生异常。为了防止服务器当机,必须处理这个异常。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 做计算题总出错怎么办 孩子字写得不好怎么办 3岁宝宝数学不好怎么办 十个月的宝宝拉肚子怎么办 刚生的宝宝拉肚子怎么办 我给兔子洗澡了怎么办 刚生的小狗脐带怎么办 宠物兔不想养了怎么办 兔子被打不动了怎么办 ai画板大小会变怎么办 和小孩生肖相冲怎么办 小孩和父母相冲怎么办 4岁宝宝抵抗力差怎么办 5岁儿童抵抗力差怎么办 格力小狗腿歪了怎么办 泰迪后腿骨折了怎么办 狗的后腿骨折了怎么办 跟丢了萨尔怎么办 去当兵欠的网贷怎么办 辅警体能过不了怎么办 蛙跳完后大腿疼怎么办 体能太差在部队怎么办 家里的钥匙丢了怎么办 lol美服更新慢怎么办 魔域密码忘了怎么办 魔域账号忘记了怎么办 魔域91密码忘了怎么办 魔域人数满了怎么办 部队玩手机被逮怎么办 脑子很笨怎么办17岁 跑步心肺功能差怎么办 剧烈运动后恶心想吐怎么办 运动后头晕想吐怎么办 跑步后反胃想吐怎么办 长跑后恶心想吐怎么办 离职前请假不批怎么办 酷派x7无限重启怎么办 钢铁雄心3资源多怎么办 汽车智能钥匙没电了怎么办 辐射3食物有辐射怎么办 辐射3玩起来很卡怎么办