Ruby - fork,exec,detach ....我们这里有竞争条件吗?

时间:2015-07-07 05:01:19

标签: ruby cygwin

简单示例,在我的平台上无效(Ruby 2.2Cygwin):

#!/usr/bin/ruby
backtt = fork { exec('mintty','/usr/bin/zsh','-i') }
Process.detach(backtt)
exit

这个小程序(从shell启动时)应该跨越一个终端窗口(mintty),然后让我回到shell提示符。

然而,虽然它创建了薄荷窗口,但之后我没有shell提示符,并且我无法在调用shell中键入任何内容。

但是当我在分离之前引入一个小延迟时,要么使用' sleep',要么在stdout上打印一些内容,它会按预期工作:

#!/usr/bin/ruby
backtt = fork { exec('mintty','/usr/bin/zsh','-i') }
sleep 1
Process.detach(backtt)
exit

为什么这有必要?

顺便说一句,我很清楚我可以(来自shell)做一个

mintty /usr/bin/zsh -i &

直接,或者我可以使用Ruby内部的系统(...... &),但这不是重点。我特别感兴趣的是Ruby中的fork/exec/detach行为。任何见解?

3 个答案:

答案 0 :(得分:6)

发帖作为答案,因为评论时间过长

虽然我不是Ruby的专家,根本不了解Cygwin,但这种情况听起来非常我来自C / C ++。

此脚本太短,因此父级的父级完成,而孙子尝试启动。

如果你在分离后和退出前放置睡眠会怎样?

如果我的理论是正确的,它也应该有效。您的程序在任何(或足够的)线程切换发生之前退出。

我称这些问题为#34;中断了握手"。虽然这是心理学术语,但它描述了会发生什么。

睡眠"放弃时间片",导致线程切换,

控制台输出(任何文件I / O)运行到信号量,也导致线程切换。

如果我的想法是正确的,它也应该有用,如果你不睡觉",只计算到1e9(取决于计算的速度),因为那时抢占式多任务使得甚至线程切换本身也没有放弃CPU。

所以这是编程中的错误(恕我直言:竞争条件在这种情况下是哲学的),但很难找到"谁"负责。涉及的内容很多。

答案 1 :(得分:0)

根据documentation

  

Process::detach通过设置单独的Ruby线程来阻止此,其唯一的工作是在进程终止时获取进程pid的状态。

注意:我无法在任何可用的操作系统上重现此行为,我只是为了格式化而将其作为答案发布。

由于Process.detach(backtt)透明地创建一个线程,我建议你尝试:

#!/usr/bin/ruby
backtt = fork { exec('mintty','/usr/bin/zsh','-i') }
#                     ⇓⇓⇓⇓⇓
Process.detach(backtt).join
exit

这不是任何意思(与愚蠢的sleep相反),因为您可能意识到底层命令应该立即返回或多或少。我不是cygwin中的大师,但它可能有一些线程的特定问题,所以,让这个线程得到处理。

答案 2 :(得分:0)

我既不是Ruby也不是Cygwin,所以我在这里提出的建议可能根本不起作用。无论如何:我猜,你甚至没有在这里遇到Ruby或Cygwin特定的bug。在一个名为" start"我多年前用C语言写过,我遇到了同样的问题。这是函数void daemonize_now()函数开头的注释:

/*
 * This is a little bit trickier than I expected: If we simply call
 * setsid(), it may fail! We have to fork() and exit(), and let our
 * child call setsid().
 *
 * Now the problem: If we fork() and exit() immediatelly, our child
 * will be killed before it ever had been run. So we need to sleep a
 * little bit. Now the question: How long? I don't know an answer. So
 * let us being killed by our child :-)
 */

所以,他的策略是:让父母等待它的孩子(这可以在孩子真正有机会做任何事之前立即完成),然后让孩子做分离部分。怎么样?让它创建一个新的进程组(它将被重新设置为init进程)。这就是我在评论中谈论的setsid()调用。它会像这样工作(C语法,你应该能够查找Ruby的正确用法并自己应用所需的更改):

parentspid = getpid();
Fork = fork();
if (Fork) {
    if (Fork == -1) { // fork() failed
        handle error
    } else { // parent, Fork is the pid of the child
        int tmp; waitpid(0, &tmp, 0);
    }
} else { // child
    if (setsid() == -1) {
        handle error - possibly by doing nothing
        and just let the parent wait ...
    } else {
        kill(parentspid, SIGUSR1);
    }
    exec(...);
}

您可以使用任何终止该过程的信号(即SIGKILL)。我使用了SIGUSR1并安装了一个退出(0)父进程的信号处理程序,因此调用者获得了成功消息。唯一需要注意的是:即使exec失败,你也会获得成功。然而,这是一个无法解决的问题,因为在成功的执行官之后,你不能再向父母发出任何信号。由于你不知道执行官什么时候会失败(如果失败了),你就会回到比赛条件部分。