GC无法在Windows上使用fork-emulation收集内存

时间:2014-09-16 11:18:45

标签: multithreading perl garbage-collection

首先让我说我对Perl没有深入的了解,所以请原谅我,如果有一些我错过的东西:)

在我正在查看的系统(在Windows环境中运行)中,我们有一个perl进程,必须下载~5000-6000个文件。由于每个文件都可以独立下载,因此我们为每个文件分叉了不同的线程。线程应该下载文件并死掉。在运行该过程时,我注意到该进程的内存高达~1.7 GB,然后由于每个进程的内存限制而死亡。

在搜索并询问一些人时,我遇到了循环引用的概念,因为垃圾收集器不会释放内存。我搜索了一下,发现Devel-Cycle包可以找出对象中是否有任何循环。我得到了这个包并添加了一行来检查进程中的主要对象是否有任何循环。 find_cycle为每个线程返回以下语句。

DBD::Oracle::db FIRSTKEY failed: handle 2 is owned by thread 256004 not current thread c0ea29c (handles can't be shared between threads and your driver may need a CLONE method added) at C:/Program Files/Perl/site/lib/Devel/Cycle.pm line 151.

我知道线程之间不能共享DB句柄。我再次查看代码并意识到在fork发生之后,子进程实际上创建了一个新的DB句柄(我想这就是为什么进程仍然继续正常运行直到达到内存限制)。我想可能会有更多来自对象中父级的数据库句柄,这些句柄未被子级使用但仍被引用。

我有的Questons -

  1. 循环引用是问题的唯一原因还是会有其他问题导致进程使用如此多的内存?

  2. 共享句柄会导致内存爆炸(换句话说就是共享数据库句柄导致GC无法释放空间)吗?

  3. 如果它确实是共享数据库句柄,我想我可以说$dbHandle = 0去除引用(如果$dbHabndle引用该特定句柄)。我在这里纠正吗?

  4. 我正在尝试查看代码以查看其他地方是否存在对父DB句柄的引用(并且至少找到了一个引用)。有没有其他方法可以做到这一点?是否有方法可以打印出对象的所有属性?

  5. 修改 并非所有线程(由于Windows中的perl fork调用)都会同时生成。它产生最多n个线程(其中n是可配置的数字)。一旦线程完成执行,该过程就会产生另一个线程。此时n设置为10,但是我将n更改为1(因此一次只运行一个额外的线程),我仍然达到了内存限制。

2 个答案:

答案 0 :(得分:5)

编辑:原来,这并不能解决Ops问题。对未来的读者来说仍然有用。

我们对你的情况并不是很了解,你的程序听起来相当复杂,只需将它分给我6000次。但我仍会尝试回答,如果我的假设是错误的,请纠正我。

看来你在Windows上。请务必注意,Windows没有fork()系统调用。当你特别注意到你" fork"时,我只是假设你实际上使用了Perl命令。在Windows上,这将尝试尽可能地模拟fork(),但这基本上意味着,您看到的所有分叉进程实际上只是原始进程中的线程,只是假装成为您的进程。为此,他们复制完整的解释器状态。有关详细信息,请参阅http://perldoc.perl.org/perlfork.html。特别是以下部分似乎适用于您:

  

资源限制

     

在操作系统的眼中,通过fork()模拟创建的伪进程只是同一进程中的线程。这意味着操作系统强加的任何进程级限制都适用于所有伪进程。这包括操作系统对打开文件,目录和套接字句柄数量,磁盘空间使用限制,内存大小限制,CPU利用率限制等施加的任何限制。

如果你分叉了很多伪进程,你需要大量内存,因为你还必须经常复制解释器状态。根据程序的复杂程度及其结构,这很可能是一个非常重要的内存。

正如http://msdn.microsoft.com/en-us/library/windows/desktop/aa366778%28v=vs.85%29.aspx告诉我们的那样,你提到的1.7GB,与一些Windows版本强加给你的2GB相差不远,作为单个进程的内存限制。

我的猜测是,你实际上只是通过产生所有那么多线程来达到这个极限,每个线程都有自己的解释器状态副本和所有东西。

使用某些线程库而不是让Perl为您模拟单个进程可能会更好。不用说(我希望)你通过拥有6000个线程而没有真正获得任何优势让我们说16。如果你试图让他们所有人同时做某事,你实际上很可能会遇到减速,这取决于如何处理线程。

答案 1 :(得分:1)

除了已经提供的评论之外,我想强调DeVadder关于Windows中fork的行为的观点,并且Perl线程可能是更好的解决方案,但是你确定DBD模块可以安全地被多个人使用进程/分支/线程等没有设置一些额外的参数?

使用DBI模块使用线程模块访问多处理代码中的SQLite DB时,我遇到了类似的错误。它是通过将DBI提供的数据库句柄的“use_immediate_transaction”选项设置为1来解决的。如果您不熟悉Perl线程的工作方式,它们不是线程,它们会创建解释器的副本以及您拥有的所有内容。内存在创建时,但即使我在每个“线程”中单独创建数据库句柄,我也会得到“数据库锁定”和其他各种错误。如果没有这些额外选项,DBD可能无法在多处理环境中正常运行。

另外,为什么要生成6000个forks,使用thread :: queue和threads模块,创建一个工人池(几个核心?)并回收工作者。你在每个叉子上做了很多开销而没有收获。