文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

python fork()多进程

2023-01-31 05:28

关注

一、理解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()函数可以产生异常。为了防止服务器当机,必须处理这个异常。


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯