15

我的开发环境是一台运行 ruby​​ 1.9.3p125 (RubyInstaller) 和 rails 3.2.8 的 Windows 机器。

在使用第三方 gems 时,一个又一次出现的问题是 Windows 上缺少 fork()。这最近阻碍了我使用几乎任何分布式测试运行 gem(如这些)的能力,因为它们依赖于 fork。

StackOverflow 上的一些旧问题试图找到解决同样问题的方法,但要么是在将 Process.spawn 添加到 ruby​​ 之前,要么是由于其他原因被迫使用旧版本的 Ruby。

建议的解决方案之一是使用 Cygwin 获得 fork() 支持,这根本不可能 - 我想我更愿意在此之前完全切换到 Linux。

另一个建议的解决方案是使用win32-process gem来获得 fork() 支持。Fork 支持已从最新版本 (0.7.0) 中删除,并且使用下一个最旧的版本 (0.6.6),它确实(某种)支持 fork 似乎不起作用,至少对于运行任何分布式测试我尝试过的宝石(Spork、Parallel tests、Hydra、Specjour,几乎所有这些)。有趣的是,gem 的作者在自述文件中提到 Process.spawn 是 Process.fork 的一种可接受的解决方法。

我看到很多信息要么暗示,要么直接陈述在 Ruby 1.9 上,该 spawn 可以在 Windows 上用作 fork 的替代品。我花了相当多的时间玩这个,基本上试图在几个引用的 gem 中用 Process.spawn 替换 Process.fork ,但没有成功。在我看来,也许行为是相似的,但并不完全相同。例如,不清楚 spawn 是否真的以与 fork 相同的方式复制整个进程,或者只是使用提供的参数创建一个新进程。还不清楚 spawn 方法是否接受另一个 ruby​​ 方法作为参数,或者只接受系统命令。文档似乎暗示它只是一个命令,但一种方法似乎有效(有点),但我可能做错了事。我认为对于某些事情,fork 只是用来创建一个“廉价线程”,在以前不支持线程的 ruby​​ 版本中。但是,这些分布式测试 gem 似乎可以合法地依赖 fork() 的全部功能,以维护项目状态,并且不为每个测试加载整个 ruby​​ 环境。这有点超出了我正常的编程职责和经验,所以我可能会做出一些不正确的假设。

所以,我的问题是,是否可以相对简单地使用 Process.spawn 来实现与 Process.fork 相同的结果,在所有情况下?我开始怀疑不是,但如果是这样,有人可以发布一个如何进行转型的例子吗?

4

1 回答 1

15

编辑:有一个常见的用例fork()可以替换为spawn()-- fork()--exec()组合。许多旧的(和现代的)UNIX 应用程序,当他们想要生成另一个进程时,会首先派生,然后进行exec调用(exec用另一个替换当前进程)。这实际上并不需要fork(),这就是为什么它可以替换为spawn(). 所以这:

if(!fork())
  exec("dir")
end

可以替换为:

Process.spawn("dir")

如果任何宝石都fork()像这样使用,修复很容易。否则,几乎是不可能的。


编辑: win32-process 的实现fork()不起作用的原因是(据我从文档中可以看出),它基本上 spawn(),根本不是fork()


不,我不认为它可以做到。您会看到,Process.spawn使用默认的空白状态和本机代码创建一个新进程。所以,虽然我可以做一些事情,比如Process.spawn('dir')将启动一个空白进程运行dir,但它不会克隆任何当前进程的状态。它与您的程序的唯一连接是父子连接。

你看,fork()这是一个非常低级的调用。例如,在 Linux 上,fork()基本上是这样的:首先,创建一个具有完全克隆的寄存器状态的新进程。然后,Linux 对所有父进程的页面进行写时复制引用。然后 Linux 会克隆一些其他进程标志。显然,所有这些操作都只能由内核完成,而 Windows 内核没有这样做的工具(也无法修补)。

从技术上讲,只有本机程序需要操作系统来获得某种类似的fork()支持。任何一层代码都需要它上面的层的合作才能完成类似fork(). 所以原生C代码需要内核的配合来fork,而Ruby理论上只需要解释器的配合来做fork。但是,Ruby 解释器没有快照/恢复功能,这将是实现分叉所必需的。正因为如此,普通的 Ruby 分叉是通过分叉解释器本身来实现的,而不是 Ruby 程序。

因此,如果您可以修补 Ruby 解释器以添加停止/启动和快照/恢复功能,那么您可以做到,但除此之外呢?我不这么认为。

那么你有什么选择呢?这是我能想到的:

  • 修补 Ruby 解释器
  • 修补fork()用于可能使用线程或生成的代码
  • 获得一个 UNIX(我建议这个)
  • 使用 Cygwin

编辑1:我不建议使用Cygwin的fork,因为它涉及特殊的Cygwin进程表,没有写时复制,这使得它非常低效。此外,它涉及很多来回跳跃和大量复制。尽可能避免它。此外,由于 Windows 不提供复制地址空间的功能,因此分叉很可能会失败,而且在很多时候都会失败(请参阅此处)。

于 2012-09-07T13:45:57.037 回答