首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python fork()多进程

python fork()多进程

作者头像
py3study
发布2020-01-09 10:10:34
1.9K0
发布2020-01-09 10:10:34
举报
文章被收录于专栏:python3python3

一、理解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-8

import os,time

print '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 python

import os,time

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' % pid
	print 'Sleeping 120 seconds...'
	time.sleep(120)

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

六、使用信号解决zombie问题

#!/usr/bin/env python
import os,time,signal
def chldHandler(signum,stackframe):
	while 1:
		try:
			result = os.waitpid(-1,os.WNOHANG)
		except:
			break
		print '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' %pid
	print '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()函数可以产生异常。为了防止服务器当机,必须处理这个异常。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-09-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档