我的问题是关于哲学而非技术问题。
目标是编写一个具有一个“主”进程和N个“工作”进程的多进程(非多线程)程序。程序是仅限linux,异步,基于事件的Web服务器,如nginx。因此,主要问题是如何产生“工人”流程。
在linux世界中有两种方式:
1)。 fork()
2)。 fork()
+ exec*()
家庭
每种方式的简短描述以及每种方式让我困惑的事情。
fork()
的第一种方式是脏的,因为分叉进程具有父内存的复制(... on-write,我知道):信号处理程序,变量,文件\套接字描述符,环境和其他,例如,堆栈和堆。总而言之,在fork之后我需要...嗯...“清除内存”,例如,禁用信号处理程序,套接字连接和其他从父母继承的可怕的东西,因为孩子有很多他不打算的数据 - 打破封装,可能产生许多副作用。
这种情况的一般方法是在分叉进程中运行无限循环来处理一些数据,并使用套接字对,管道或共享内存做一些魔术,以便在fork()
之前和之后创建父和子之间的通信通道,因为套接字描述符在子节点中重新打开,并使用与父节点相同的套接字。
此外,这是nginx-way:它有一个可执行的二进制文件,它使用fork()
来生成子进程。
第二种方式与第一种方式类似,但在运行外部二进制文件exec*()
之后的子进程中使用fork()
函数之一有所不同。一个重要的事情是exec*()
在当前(分叉)进程内存中加载二进制文件,自动清除堆栈,堆并执行所有其他讨厌的工作,因此fork看起来像一个明确的新程序实例而没有父内存的副本或其他东西其他垃圾。
在父和子之间建立通信还有另一个问题:因为exec*()
之后的分叉进程删除了从父进程继承的所有数据,我需要以某种方式在父对象和子进程之间创建一个套接字对。例如,在父级和等待子连接中创建额外的侦听套接字(域或在另一个端口中),子级应在初始化后连接到父级。
第一种方法很简单,但让我感到困惑,那不是一个明确的过程,只是父内存的副本,有许多可能的副作用和垃圾,需要记住,分叉进程对父进程有很多依赖性码。第二种方式需要更多时间来支持两个二进制文件,而不像单文件解决方案那样优雅。也许,最好的方法是使用fork()
进行流程创建,然后使用exec*()
调用来清除内存,但我无法找到第二步的任何解决方案。
总之,我需要帮助来决定使用哪种方式:创建像nginx这样的单文件可执行文件,然后使用fork()
,或者创建两个单独的文件,一个带有“server”,另一个带有“worker” ,并从“服务器”使用fork()
+ exec*(worker)
N次,并希望了解每种方式的利弊,也许我错过了一些东西。
答案 0 :(得分:0)
作为Linux程序员,您拥有丰富的多线程处理能力库。看看pthread和朋友们。
如果你需要一个处理请求,那么自古以来,fork和朋友就是使用最广泛的。
答案 1 :(得分:0)
对于多进程解决方案,fork和fork + exec这两个选项几乎是等效的,取决于子进程和父进程上下文。如果子进程执行父进程的文本(二进制)并需要全部或部分父母的工作人员(描述符,信号等) - 这是使用fork的标志。如果孩子应该执行一个新的二进制文件并且不需要父母的工作人员 - 看起来fork + exec更合适。
pthread库中还有一个很好的功能 - pthread_atfork()。 它允许注册将在fork之前和之后调用的处理程序。 这些处理程序可以执行所有必要的工作(例如,关闭文件描述符)。