【问题标题】:pcntl_fork child process memorypcntl_fork 子进程内存
【发布时间】:2020-11-28 06:38:50
【问题描述】:

当通过pcntl_fork 分叉时,我看到了奇怪的行为。

一个正在运行的守护进程的简单脚本

pcntl_fork();
while (true) {
    sleep(1);
}

让我们比较一下父母和孩子的内存使用情况

可以看出,子进程占用的内存少了大约三分之一。如果你再深入一点,比较两个进程的pmap -x 1172 1173 的输出,我们会看到下图 如果我理解正确,那么在启动时 PHP 会为自己以及它的所有模块分配内存,并且这个余量非常重要,特别是如果您在一个实例上运行了许多守护程序。

事实证明,您可以通过一种巧妙的方式减少守护程序的内存使用量。

if (pcntl_fork() > 0) {
    die();
}
while (true) {
    sleep(1);
}

这里可能有什么问题?只是你必须在必要时花时间分配内存?

【问题讨论】:

    标签: php memory


    【解决方案1】:

    这是因为在主进程启动时,他需要导入一些配置文件,如php.ini,一些库,...

    当你 fork 主进程时,子进程会重置一些参数 (fork(2) — Linux manual page),比如父进程的内存锁、挂起的信号、一些未完成的异步 I/O 操作……

    就像子进程已经运行了很短的时间并且他几乎没有做任何事情一样,它不需要那么多内存但是如果你将子进程用作主进程并结束,子进程将使用比父级更多的内存。

    因此,您可能认为一段时间后分叉您的主进程,重置此参数可能是个好主意,但这不是因为:

    • 在此示例中,您将比较两个进程,然后它们几乎什么也没做,因此这里的内存使用量最多的是辅助注册,但在正常执行中并不常见。
    • 您将花费资源来派生一个进程,尤其是在您有很多变量的情况下,因此您将需要更多资源来派生该进程,这样您将节省重置参数。
    • 即使您节省了一些内存,您的代码也会变得非常复杂,因此请理解和维护。

    希望对你有所帮助。

    【讨论】:

    • > 如果将子进程用作主进程并结束,则子进程将使用比父进程更多的内存。 @Percodes,你能解释一下,为什么子进程会使用更多的内存吗?
    • 主进程比子进程使用更多的内存,因为它做了更多的事情,比如导入文件,所以它需要在内存上保存一些注册表(比如指向它已经导入的文件的指针) 并且子进程什么也没做,所以它在内存上没有任何注册。当您将使用子进程并打开一些文件(如requires)时,它会将指针保存在您可以看到here 的内存上,它将使用与主进程相同的内存。
    • 而且你会花费一些内存和 CPU 来分叉进程,我相信你会节省更多的资源来分叉进程。
    • 想象你有一辆车(主进程),你开着它开一会儿,然后克隆它(分叉主进程)并重置公里(重置所有参数)。如果你在这一刻计算公里数,克隆汽车的公里数会更少(子进程使用更少的内存)但这只是因为你已经重置了它。如果你将克隆汽车作为主车(子进程成为主进程)进程)一段时间后,克隆的汽车将有更多公里(子进程将使用更多内存)。我希望这样会更容易理解。 :))
    【解决方案2】:

    首先你必须了解 PHP pcntl_fork 说 - 分叉当前正在运行的进程

    pcntl_fork() 函数创建一个不同于 父进程仅在其PID和PPID中。请查看您系统的 fork(2) 手册页 了解有关 fork 如何在您的 系统。

    这里需要注意的是你必须了解你的系统 Fork(2) 进程。所以 Fork 是依赖于操作系统的

    根据this页面有一些fork(2)的例子

    fork() 导致创建一个新进程。新流程(子 进程)是调用进程(父进程)的精确副本 以下情况除外:

    • 子进程具有唯一的进程 ID,该 ID 也不匹配任何现有的进程组 ID。
    • 子进程具有不同的父进程 ID(即父进程的进程 ID)。
    • 子进程只有一个线程。
    • 子进程拥有自己的父描述符副本。这些描述符引用相同的底层对象,因此,对于 例如,文件对象中的文件指针在子节点之间共享 和父级,以便子级描述符上的 lseek(2) 进程可能会影响父进程的后续读取(2)或写入(2)。 这个描述符复制也被shell用来建立 新创建的流程的标准输入和输出以及 设置管道。
    • 子进程没有 fcntl(2) 样式的文件锁。
    • 子进程的资源利用率设置为0;请参阅 getrusage(2)。
    • 所有间隔定时器都被清除;参见 setitimer(2)。
    • 子进程的信号量撤消值设置为 0;参见 semop(2)。
    • 子进程的待处理信号集为空。
    • 子进程没有内存锁;参见 mlock(2) 和 mlockall(2)。

    一般来说,子进程应该调用 _exit(2) 而不是 退出(3)。否则,任何同时存在于父级中的 stdio 缓冲区 孩子会被冲两次。同样,_exit(2) 应该用于 防止 atexit(3) 例程被调用两次(一次在父 一次在孩子身上)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-02
      • 1970-01-01
      • 2012-07-01
      • 2011-04-08
      • 2014-10-19
      • 2020-04-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多